From 6bc832f50a41cdffce9eb09c7867767c5466d779 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 9 Mar 2021 16:27:12 +1100 Subject: [PATCH 01/51] DataExtractionNotification type created and added to proto --- .../DataExtractionNotificationManager.kt | 4 + .../messaging/jobs/MessageReceiveJob.kt | 2 +- .../control/DataExtractionNotification.kt | 81 ++ .../sending_receiving/MessageReceiver.kt | 1 + libsignal/protobuf/SignalService.proto | 21 +- .../internal/push/SignalServiceProtos.java | 1085 +++++++++++++++-- 6 files changed, 1065 insertions(+), 129 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/sskenvironment/DataExtractionNotificationManager.kt create mode 100644 libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/DataExtractionNotificationManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/DataExtractionNotificationManager.kt new file mode 100644 index 0000000000..0c8fa779d4 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/DataExtractionNotificationManager.kt @@ -0,0 +1,4 @@ +package org.thoughtcrime.securesms.sskenvironment + +class DataExtractionNotificationManager { +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt index d6b2bfff8f..a69e9162ea 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt @@ -16,7 +16,7 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val override val maxFailureCount: Int = 10 companion object { val TAG = MessageReceiveJob::class.qualifiedName - val KEY: String = "AttachmentUploadJob" + val KEY: String = "MessageReceiveJob" //keys used for database storage purpose private val KEY_DATA = "data" diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt new file mode 100644 index 0000000000..a612584af8 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt @@ -0,0 +1,81 @@ +package org.session.libsession.messaging.messages.control + +import com.google.protobuf.ByteString +import org.session.libsignal.libsignal.ecc.ECKeyPair +import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.session.libsignal.utilities.logging.Log +import java.lang.Exception + +class DataExtractionNotification(): ControlMessage() { + var kind: Kind? = null + + // Kind enum + sealed class Kind { + class Screenshot() : Kind() + class MediaSaved(val timestanp: Long) : Kind() + + val description: String = run { + when(this) { + is Screenshot -> "screenshot" + is MediaSaved -> "mediaSaved" + } + } + } + + companion object { + const val TAG = "DataExtractionNotification" + + fun fromProto(proto: SignalServiceProtos.Content): DataExtractionNotification? { + val dataExtractionNotification = proto.dataExtractionNotification ?: return null + val kind: Kind + when(dataExtractionNotification.type) { + SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT -> kind = Kind.Screenshot() + SignalServiceProtos.DataExtractionNotification.Type.MEDIA_SAVED -> { + val timestamp = if (dataExtractionNotification.hasTimestamp()) dataExtractionNotification.timestamp else 0 + kind = Kind.MediaSaved(timestamp) + } + } + return DataExtractionNotification(kind) + } + } + + //constructor + internal constructor(kind: Kind) : this() { + this.kind = kind + } + + // MARK: Validation + override fun isValid(): Boolean { + if (!super.isValid()) return false + val kind = kind ?: return false + return when(kind) { + is Kind.Screenshot -> true + is Kind.MediaSaved -> kind.timestanp > 0 + } + } + + override fun toProto(): SignalServiceProtos.Content? { + val kind = kind + if (kind == null) { + Log.w(TAG, "Couldn't construct data extraction notification proto from: $this") + return null + } + try { + val dataExtractionNotification = SignalServiceProtos.DataExtractionNotification.newBuilder() + when(kind) { + is Kind.Screenshot -> dataExtractionNotification.type = SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT + is Kind.MediaSaved -> { + dataExtractionNotification.type = SignalServiceProtos.DataExtractionNotification.Type.MEDIA_SAVED + dataExtractionNotification.timestamp = kind.timestanp + } + } + val contentProto = SignalServiceProtos.Content.newBuilder() + contentProto.dataExtractionNotification = dataExtractionNotification.build() + return contentProto.build() + } catch (e: Exception) { + Log.w(TAG, "Couldn't construct data extraction notification proto from: $this") + return null + } + } + +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index 5b661327cb..27e03fbfc9 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -120,6 +120,7 @@ object MessageReceiver { val message: Message = ReadReceipt.fromProto(proto) ?: TypingIndicator.fromProto(proto) ?: ClosedGroupControlMessage.fromProto(proto) ?: + DataExtractionNotification.fromProto(proto) ?: ExpirationTimerUpdate.fromProto(proto) ?: ConfigurationMessage.fromProto(proto) ?: VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage diff --git a/libsignal/protobuf/SignalService.proto b/libsignal/protobuf/SignalService.proto index ac5d8c7092..614b5e65f2 100644 --- a/libsignal/protobuf/SignalService.proto +++ b/libsignal/protobuf/SignalService.proto @@ -36,10 +36,11 @@ message TypingMessage { } message Content { - optional DataMessage dataMessage = 1; - optional ReceiptMessage receiptMessage = 5; - optional TypingMessage typingMessage = 6; - optional ConfigurationMessage configurationMessage = 7; + optional DataMessage dataMessage = 1; + optional ReceiptMessage receiptMessage = 5; + optional TypingMessage typingMessage = 6; + optional ConfigurationMessage configurationMessage = 7; + optional DataExtractionNotification dataExtractionNotification = 82; } message ClosedGroupCiphertextMessageWrapper { @@ -56,6 +57,18 @@ message KeyPair { required bytes privateKey = 2; } +message DataExtractionNotification { + + enum Type { + SCREENSHOT = 1; + MEDIA_SAVED = 2; // timestamp + } + + // @required + required Type type = 1; + optional uint64 timestamp = 2; +} + message DataMessage { enum Flags { diff --git a/libsignal/src/main/java/org/session/libsignal/service/internal/push/SignalServiceProtos.java b/libsignal/src/main/java/org/session/libsignal/service/internal/push/SignalServiceProtos.java index 2f53875e25..0e4feba1ed 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/internal/push/SignalServiceProtos.java +++ b/libsignal/src/main/java/org/session/libsignal/service/internal/push/SignalServiceProtos.java @@ -1740,6 +1740,20 @@ public final class SignalServiceProtos { * optional .signalservice.ConfigurationMessage configurationMessage = 7; */ org.session.libsignal.service.internal.push.SignalServiceProtos.ConfigurationMessageOrBuilder getConfigurationMessageOrBuilder(); + + // optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + boolean hasDataExtractionNotification(); + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification getDataExtractionNotification(); + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotificationOrBuilder getDataExtractionNotificationOrBuilder(); } /** * Protobuf type {@code signalservice.Content} @@ -1844,6 +1858,19 @@ public final class SignalServiceProtos { bitField0_ |= 0x00000008; break; } + case 658: { + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Builder subBuilder = null; + if (((bitField0_ & 0x00000010) == 0x00000010)) { + subBuilder = dataExtractionNotification_.toBuilder(); + } + dataExtractionNotification_ = input.readMessage(org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(dataExtractionNotification_); + dataExtractionNotification_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000010; + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -1972,11 +1999,34 @@ public final class SignalServiceProtos { return configurationMessage_; } + // optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + public static final int DATAEXTRACTIONNOTIFICATION_FIELD_NUMBER = 82; + private org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification dataExtractionNotification_; + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + public boolean hasDataExtractionNotification() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + public org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification getDataExtractionNotification() { + return dataExtractionNotification_; + } + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + public org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotificationOrBuilder getDataExtractionNotificationOrBuilder() { + return dataExtractionNotification_; + } + private void initFields() { dataMessage_ = org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage.getDefaultInstance(); receiptMessage_ = org.session.libsignal.service.internal.push.SignalServiceProtos.ReceiptMessage.getDefaultInstance(); typingMessage_ = org.session.libsignal.service.internal.push.SignalServiceProtos.TypingMessage.getDefaultInstance(); configurationMessage_ = org.session.libsignal.service.internal.push.SignalServiceProtos.ConfigurationMessage.getDefaultInstance(); + dataExtractionNotification_ = org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.getDefaultInstance(); } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -1995,6 +2045,12 @@ public final class SignalServiceProtos { return false; } } + if (hasDataExtractionNotification()) { + if (!getDataExtractionNotification().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } memoizedIsInitialized = 1; return true; } @@ -2014,6 +2070,9 @@ public final class SignalServiceProtos { if (((bitField0_ & 0x00000008) == 0x00000008)) { output.writeMessage(7, configurationMessage_); } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeMessage(82, dataExtractionNotification_); + } getUnknownFields().writeTo(output); } @@ -2039,6 +2098,10 @@ public final class SignalServiceProtos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(7, configurationMessage_); } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(82, dataExtractionNotification_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -2151,6 +2214,7 @@ public final class SignalServiceProtos { getReceiptMessageFieldBuilder(); getTypingMessageFieldBuilder(); getConfigurationMessageFieldBuilder(); + getDataExtractionNotificationFieldBuilder(); } } private static Builder create() { @@ -2183,6 +2247,12 @@ public final class SignalServiceProtos { configurationMessageBuilder_.clear(); } bitField0_ = (bitField0_ & ~0x00000008); + if (dataExtractionNotificationBuilder_ == null) { + dataExtractionNotification_ = org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.getDefaultInstance(); + } else { + dataExtractionNotificationBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000010); return this; } @@ -2243,6 +2313,14 @@ public final class SignalServiceProtos { } else { result.configurationMessage_ = configurationMessageBuilder_.build(); } + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } + if (dataExtractionNotificationBuilder_ == null) { + result.dataExtractionNotification_ = dataExtractionNotification_; + } else { + result.dataExtractionNotification_ = dataExtractionNotificationBuilder_.build(); + } result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -2271,6 +2349,9 @@ public final class SignalServiceProtos { if (other.hasConfigurationMessage()) { mergeConfigurationMessage(other.getConfigurationMessage()); } + if (other.hasDataExtractionNotification()) { + mergeDataExtractionNotification(other.getDataExtractionNotification()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -2288,6 +2369,12 @@ public final class SignalServiceProtos { return false; } } + if (hasDataExtractionNotification()) { + if (!getDataExtractionNotification().isInitialized()) { + + return false; + } + } return true; } @@ -2778,6 +2865,123 @@ public final class SignalServiceProtos { return configurationMessageBuilder_; } + // optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + private org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification dataExtractionNotification_ = org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification, org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Builder, org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotificationOrBuilder> dataExtractionNotificationBuilder_; + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + public boolean hasDataExtractionNotification() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + public org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification getDataExtractionNotification() { + if (dataExtractionNotificationBuilder_ == null) { + return dataExtractionNotification_; + } else { + return dataExtractionNotificationBuilder_.getMessage(); + } + } + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + public Builder setDataExtractionNotification(org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification value) { + if (dataExtractionNotificationBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + dataExtractionNotification_ = value; + onChanged(); + } else { + dataExtractionNotificationBuilder_.setMessage(value); + } + bitField0_ |= 0x00000010; + return this; + } + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + public Builder setDataExtractionNotification( + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Builder builderForValue) { + if (dataExtractionNotificationBuilder_ == null) { + dataExtractionNotification_ = builderForValue.build(); + onChanged(); + } else { + dataExtractionNotificationBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000010; + return this; + } + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + public Builder mergeDataExtractionNotification(org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification value) { + if (dataExtractionNotificationBuilder_ == null) { + if (((bitField0_ & 0x00000010) == 0x00000010) && + dataExtractionNotification_ != org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.getDefaultInstance()) { + dataExtractionNotification_ = + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.newBuilder(dataExtractionNotification_).mergeFrom(value).buildPartial(); + } else { + dataExtractionNotification_ = value; + } + onChanged(); + } else { + dataExtractionNotificationBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000010; + return this; + } + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + public Builder clearDataExtractionNotification() { + if (dataExtractionNotificationBuilder_ == null) { + dataExtractionNotification_ = org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.getDefaultInstance(); + onChanged(); + } else { + dataExtractionNotificationBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000010); + return this; + } + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + public org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Builder getDataExtractionNotificationBuilder() { + bitField0_ |= 0x00000010; + onChanged(); + return getDataExtractionNotificationFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + public org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotificationOrBuilder getDataExtractionNotificationOrBuilder() { + if (dataExtractionNotificationBuilder_ != null) { + return dataExtractionNotificationBuilder_.getMessageOrBuilder(); + } else { + return dataExtractionNotification_; + } + } + /** + * optional .signalservice.DataExtractionNotification dataExtractionNotification = 82; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification, org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Builder, org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotificationOrBuilder> + getDataExtractionNotificationFieldBuilder() { + if (dataExtractionNotificationBuilder_ == null) { + dataExtractionNotificationBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification, org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Builder, org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotificationOrBuilder>( + dataExtractionNotification_, + getParentForChildren(), + isClean()); + dataExtractionNotification_ = null; + } + return dataExtractionNotificationBuilder_; + } + // @@protoc_insertion_point(builder_scope:signalservice.Content) } @@ -3899,6 +4103,622 @@ public final class SignalServiceProtos { // @@protoc_insertion_point(class_scope:signalservice.KeyPair) } + public interface DataExtractionNotificationOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // required .signalservice.DataExtractionNotification.Type type = 1; + /** + * required .signalservice.DataExtractionNotification.Type type = 1; + * + *
+     * @required
+     * 
+ */ + boolean hasType(); + /** + * required .signalservice.DataExtractionNotification.Type type = 1; + * + *
+     * @required
+     * 
+ */ + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Type getType(); + + // optional uint64 timestamp = 2; + /** + * optional uint64 timestamp = 2; + */ + boolean hasTimestamp(); + /** + * optional uint64 timestamp = 2; + */ + long getTimestamp(); + } + /** + * Protobuf type {@code signalservice.DataExtractionNotification} + */ + public static final class DataExtractionNotification extends + com.google.protobuf.GeneratedMessage + implements DataExtractionNotificationOrBuilder { + // Use DataExtractionNotification.newBuilder() to construct. + private DataExtractionNotification(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private DataExtractionNotification(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final DataExtractionNotification defaultInstance; + public static DataExtractionNotification getDefaultInstance() { + return defaultInstance; + } + + public DataExtractionNotification getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private DataExtractionNotification( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + int rawValue = input.readEnum(); + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Type value = org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Type.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(1, rawValue); + } else { + bitField0_ |= 0x00000001; + type_ = value; + } + break; + } + case 16: { + bitField0_ |= 0x00000002; + timestamp_ = input.readUInt64(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.service.internal.push.SignalServiceProtos.internal_static_signalservice_DataExtractionNotification_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.service.internal.push.SignalServiceProtos.internal_static_signalservice_DataExtractionNotification_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.class, org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public DataExtractionNotification parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new DataExtractionNotification(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + /** + * Protobuf enum {@code signalservice.DataExtractionNotification.Type} + */ + public enum Type + implements com.google.protobuf.ProtocolMessageEnum { + /** + * SCREENSHOT = 1; + */ + SCREENSHOT(0, 1), + /** + * MEDIA_SAVED = 2; + * + *
+       * timestamp
+       * 
+ */ + MEDIA_SAVED(1, 2), + ; + + /** + * SCREENSHOT = 1; + */ + public static final int SCREENSHOT_VALUE = 1; + /** + * MEDIA_SAVED = 2; + * + *
+       * timestamp
+       * 
+ */ + public static final int MEDIA_SAVED_VALUE = 2; + + + public final int getNumber() { return value; } + + public static Type valueOf(int value) { + switch (value) { + case 1: return SCREENSHOT; + case 2: return MEDIA_SAVED; + default: return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public Type findValueByNumber(int number) { + return Type.valueOf(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(index); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.getDescriptor().getEnumTypes().get(0); + } + + private static final Type[] VALUES = values(); + + public static Type valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } + + private final int index; + private final int value; + + private Type(int index, int value) { + this.index = index; + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:signalservice.DataExtractionNotification.Type) + } + + private int bitField0_; + // required .signalservice.DataExtractionNotification.Type type = 1; + public static final int TYPE_FIELD_NUMBER = 1; + private org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Type type_; + /** + * required .signalservice.DataExtractionNotification.Type type = 1; + * + *
+     * @required
+     * 
+ */ + public boolean hasType() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required .signalservice.DataExtractionNotification.Type type = 1; + * + *
+     * @required
+     * 
+ */ + public org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Type getType() { + return type_; + } + + // optional uint64 timestamp = 2; + public static final int TIMESTAMP_FIELD_NUMBER = 2; + private long timestamp_; + /** + * optional uint64 timestamp = 2; + */ + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional uint64 timestamp = 2; + */ + public long getTimestamp() { + return timestamp_; + } + + private void initFields() { + type_ = org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT; + timestamp_ = 0L; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (!hasType()) { + memoizedIsInitialized = 0; + return false; + } + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeEnum(1, type_.getNumber()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeUInt64(2, timestamp_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(1, type_.getNumber()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(2, timestamp_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code signalservice.DataExtractionNotification} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotificationOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.service.internal.push.SignalServiceProtos.internal_static_signalservice_DataExtractionNotification_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.service.internal.push.SignalServiceProtos.internal_static_signalservice_DataExtractionNotification_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.class, org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Builder.class); + } + + // Construct using org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + type_ = org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT; + bitField0_ = (bitField0_ & ~0x00000001); + timestamp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.session.libsignal.service.internal.push.SignalServiceProtos.internal_static_signalservice_DataExtractionNotification_descriptor; + } + + public org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification getDefaultInstanceForType() { + return org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.getDefaultInstance(); + } + + public org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification build() { + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification buildPartial() { + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification result = new org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.type_ = type_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.timestamp_ = timestamp_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification) { + return mergeFrom((org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification other) { + if (other == org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.getDefaultInstance()) return this; + if (other.hasType()) { + setType(other.getType()); + } + if (other.hasTimestamp()) { + setTimestamp(other.getTimestamp()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (!hasType()) { + + return false; + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // required .signalservice.DataExtractionNotification.Type type = 1; + private org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Type type_ = org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT; + /** + * required .signalservice.DataExtractionNotification.Type type = 1; + * + *
+       * @required
+       * 
+ */ + public boolean hasType() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required .signalservice.DataExtractionNotification.Type type = 1; + * + *
+       * @required
+       * 
+ */ + public org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Type getType() { + return type_; + } + /** + * required .signalservice.DataExtractionNotification.Type type = 1; + * + *
+       * @required
+       * 
+ */ + public Builder setType(org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Type value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + type_ = value; + onChanged(); + return this; + } + /** + * required .signalservice.DataExtractionNotification.Type type = 1; + * + *
+       * @required
+       * 
+ */ + public Builder clearType() { + bitField0_ = (bitField0_ & ~0x00000001); + type_ = org.session.libsignal.service.internal.push.SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT; + onChanged(); + return this; + } + + // optional uint64 timestamp = 2; + private long timestamp_ ; + /** + * optional uint64 timestamp = 2; + */ + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional uint64 timestamp = 2; + */ + public long getTimestamp() { + return timestamp_; + } + /** + * optional uint64 timestamp = 2; + */ + public Builder setTimestamp(long value) { + bitField0_ |= 0x00000002; + timestamp_ = value; + onChanged(); + return this; + } + /** + * optional uint64 timestamp = 2; + */ + public Builder clearTimestamp() { + bitField0_ = (bitField0_ & ~0x00000002); + timestamp_ = 0L; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:signalservice.DataExtractionNotification) + } + + static { + defaultInstance = new DataExtractionNotification(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:signalservice.DataExtractionNotification) + } + public interface DataMessageOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -32398,6 +33218,11 @@ public final class SignalServiceProtos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_KeyPair_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_signalservice_DataExtractionNotification_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_signalservice_DataExtractionNotification_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_DataMessage_descriptor; private static @@ -32536,126 +33361,132 @@ public final class SignalServiceProtos { "P_CIPHERTEXT\020\007\"{\n\rTypingMessage\022\021\n\ttimes" + "tamp\030\001 \001(\004\0223\n\006action\030\002 \001(\0162#.signalservi" + "ce.TypingMessage.Action\"\"\n\006Action\022\013\n\007STA" + - "RTED\020\000\022\013\n\007STOPPED\020\001\"\351\001\n\007Content\022/\n\013dataM", + "RTED\020\000\022\013\n\007STOPPED\020\001\"\270\002\n\007Content\022/\n\013dataM", "essage\030\001 \001(\0132\032.signalservice.DataMessage" + "\0225\n\016receiptMessage\030\005 \001(\0132\035.signalservice" + ".ReceiptMessage\0223\n\rtypingMessage\030\006 \001(\0132\034" + ".signalservice.TypingMessage\022A\n\024configur" + "ationMessage\030\007 \001(\0132#.signalservice.Confi" + - "gurationMessage\"U\n#ClosedGroupCiphertext" + - "MessageWrapper\022\022\n\nciphertext\030\001 \001(\014\022\032\n\022ep" + - "hemeralPublicKey\030\002 \001(\014\"0\n\007KeyPair\022\021\n\tpub" + - "licKey\030\001 \002(\014\022\022\n\nprivateKey\030\002 \002(\014\"\357\024\n\013Dat" + - "aMessage\022\014\n\004body\030\001 \001(\t\0225\n\013attachments\030\002 ", - "\003(\0132 .signalservice.AttachmentPointer\022*\n" + - "\005group\030\003 \001(\0132\033.signalservice.GroupContex" + - "t\022\r\n\005flags\030\004 \001(\r\022\023\n\013expireTimer\030\005 \001(\r\022\022\n" + - "\nprofileKey\030\006 \001(\014\022\021\n\ttimestamp\030\007 \001(\004\022/\n\005" + - "quote\030\010 \001(\0132 .signalservice.DataMessage." + - "Quote\0223\n\007contact\030\t \003(\0132\".signalservice.D" + - "ataMessage.Contact\0223\n\007preview\030\n \003(\0132\".si" + - "gnalservice.DataMessage.Preview\0227\n\007profi" + - "le\030e \001(\0132&.signalservice.DataMessage.Lok" + - "iProfile\022W\n\031closedGroupControlMessage\030h ", - "\001(\01324.signalservice.DataMessage.ClosedGr" + - "oupControlMessage\022\022\n\nsyncTarget\030i \001(\t\0226\n" + - "\016publicChatInfo\030\347\007 \001(\0132\035.signalservice.P" + - "ublicChatInfo\032\225\002\n\005Quote\022\n\n\002id\030\001 \001(\004\022\016\n\006a" + - "uthor\030\002 \001(\t\022\014\n\004text\030\003 \001(\t\022F\n\013attachments" + - "\030\004 \003(\01321.signalservice.DataMessage.Quote" + - ".QuotedAttachment\032\231\001\n\020QuotedAttachment\022\023" + - "\n\013contentType\030\001 \001(\t\022\020\n\010fileName\030\002 \001(\t\0223\n" + - "\tthumbnail\030\003 \001(\0132 .signalservice.Attachm" + - "entPointer\022\r\n\005flags\030\004 \001(\r\"\032\n\005Flags\022\021\n\rVO", - "ICE_MESSAGE\020\001\032\304\010\n\007Contact\0225\n\004name\030\001 \001(\0132" + - "\'.signalservice.DataMessage.Contact.Name" + - "\0228\n\006number\030\003 \003(\0132(.signalservice.DataMes" + - "sage.Contact.Phone\0227\n\005email\030\004 \003(\0132(.sign" + - "alservice.DataMessage.Contact.Email\022A\n\007a" + - "ddress\030\005 \003(\01320.signalservice.DataMessage" + - ".Contact.PostalAddress\0229\n\006avatar\030\006 \001(\0132)" + - ".signalservice.DataMessage.Contact.Avata" + - "r\022\024\n\014organization\030\007 \001(\t\032v\n\004Name\022\021\n\tgiven" + - "Name\030\001 \001(\t\022\022\n\nfamilyName\030\002 \001(\t\022\016\n\006prefix", - "\030\003 \001(\t\022\016\n\006suffix\030\004 \001(\t\022\022\n\nmiddleName\030\005 \001" + - "(\t\022\023\n\013displayName\030\006 \001(\t\032\226\001\n\005Phone\022\r\n\005val" + - "ue\030\001 \001(\t\022;\n\004type\030\002 \001(\0162-.signalservice.D" + - "ataMessage.Contact.Phone.Type\022\r\n\005label\030\003" + - " \001(\t\"2\n\004Type\022\010\n\004HOME\020\001\022\n\n\006MOBILE\020\002\022\010\n\004WO" + - "RK\020\003\022\n\n\006CUSTOM\020\004\032\226\001\n\005Email\022\r\n\005value\030\001 \001(" + - "\t\022;\n\004type\030\002 \001(\0162-.signalservice.DataMess" + - "age.Contact.Email.Type\022\r\n\005label\030\003 \001(\t\"2\n" + - "\004Type\022\010\n\004HOME\020\001\022\n\n\006MOBILE\020\002\022\010\n\004WORK\020\003\022\n\n" + - "\006CUSTOM\020\004\032\201\002\n\rPostalAddress\022C\n\004type\030\001 \001(", - "\01625.signalservice.DataMessage.Contact.Po" + - "stalAddress.Type\022\r\n\005label\030\002 \001(\t\022\016\n\006stree" + - "t\030\003 \001(\t\022\r\n\005pobox\030\004 \001(\t\022\024\n\014neighborhood\030\005" + - " \001(\t\022\014\n\004city\030\006 \001(\t\022\016\n\006region\030\007 \001(\t\022\020\n\010po" + - "stcode\030\010 \001(\t\022\017\n\007country\030\t \001(\t\"&\n\004Type\022\010\n" + - "\004HOME\020\001\022\010\n\004WORK\020\002\022\n\n\006CUSTOM\020\003\032M\n\006Avatar\022" + - "0\n\006avatar\030\001 \001(\0132 .signalservice.Attachme" + - "ntPointer\022\021\n\tisProfile\030\002 \001(\010\032V\n\007Preview\022" + - "\013\n\003url\030\001 \001(\t\022\r\n\005title\030\002 \001(\t\022/\n\005image\030\003 \001" + - "(\0132 .signalservice.AttachmentPointer\032:\n\013", - "LokiProfile\022\023\n\013displayName\030\001 \001(\t\022\026\n\016prof" + - "ilePicture\030\002 \001(\t\032\221\004\n\031ClosedGroupControlM" + - "essage\022G\n\004type\030\001 \002(\01629.signalservice.Dat" + - "aMessage.ClosedGroupControlMessage.Type\022" + - "\021\n\tpublicKey\030\002 \001(\014\022\014\n\004name\030\003 \001(\t\0221\n\021encr" + - "yptionKeyPair\030\004 \001(\0132\026.signalservice.KeyP" + - "air\022\017\n\007members\030\005 \003(\014\022\016\n\006admins\030\006 \003(\014\022U\n\010" + - "wrappers\030\007 \003(\0132C.signalservice.DataMessa" + - "ge.ClosedGroupControlMessage.KeyPairWrap" + - "per\032=\n\016KeyPairWrapper\022\021\n\tpublicKey\030\001 \002(\014", - "\022\030\n\020encryptedKeyPair\030\002 \002(\014\"\237\001\n\004Type\022\007\n\003N" + - "EW\020\001\022\n\n\006UPDATE\020\002\022\027\n\023ENCRYPTION_KEY_PAIR\020" + - "\003\022\017\n\013NAME_CHANGE\020\004\022\021\n\rMEMBERS_ADDED\020\005\022\023\n" + - "\017MEMBERS_REMOVED\020\006\022\017\n\013MEMBER_LEFT\020\007\022\037\n\033E" + - "NCRYPTION_KEY_PAIR_REQUEST\020\010\"$\n\005Flags\022\033\n" + - "\027EXPIRATION_TIMER_UPDATE\020\002\"\316\003\n\024Configura" + - "tionMessage\022E\n\014closedGroups\030\001 \003(\0132/.sign" + - "alservice.ConfigurationMessage.ClosedGro" + - "up\022\022\n\nopenGroups\030\002 \003(\t\022\023\n\013displayName\030\003 " + - "\001(\t\022\026\n\016profilePicture\030\004 \001(\t\022\022\n\nprofileKe", - "y\030\005 \001(\014\022=\n\010contacts\030\006 \003(\0132+.signalservic" + - "e.ConfigurationMessage.Contact\032\202\001\n\013Close" + - "dGroup\022\021\n\tpublicKey\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\022" + - "1\n\021encryptionKeyPair\030\003 \001(\0132\026.signalservi" + - "ce.KeyPair\022\017\n\007members\030\004 \003(\014\022\016\n\006admins\030\005 " + - "\003(\014\032V\n\007Contact\022\021\n\tpublicKey\030\001 \002(\014\022\014\n\004nam" + - "e\030\002 \002(\t\022\026\n\016profilePicture\030\003 \001(\t\022\022\n\nprofi" + - "leKey\030\004 \001(\014\"u\n\016ReceiptMessage\0220\n\004type\030\001 " + - "\001(\0162\".signalservice.ReceiptMessage.Type\022" + - "\021\n\ttimestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010DELIVERY\020\000", - "\022\010\n\004READ\020\001\"\354\001\n\021AttachmentPointer\022\n\n\002id\030\001" + - " \001(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014" + - "\n\004size\030\004 \001(\r\022\021\n\tthumbnail\030\005 \001(\014\022\016\n\006diges" + - "t\030\006 \001(\014\022\020\n\010fileName\030\007 \001(\t\022\r\n\005flags\030\010 \001(\r" + - "\022\r\n\005width\030\t \001(\r\022\016\n\006height\030\n \001(\r\022\017\n\007capti" + - "on\030\013 \001(\t\022\013\n\003url\030e \001(\t\"\032\n\005Flags\022\021\n\rVOICE_" + - "MESSAGE\020\001\"\243\002\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022." + - "\n\004type\030\002 \001(\0162 .signalservice.GroupContex" + - "t.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030\004 \003(\t\0220\n" + - "\006avatar\030\005 \001(\0132 .signalservice.Attachment", - "Pointer\022\016\n\006admins\030\006 \003(\t\022\023\n\nnewMembers\030\346\007" + - " \003(\t\022\027\n\016removedMembers\030\347\007 \003(\t\"H\n\004Type\022\013\n" + - "\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n\007DELIVER\020\002\022\010\n\004Q" + - "UIT\020\003\022\020\n\014REQUEST_INFO\020\004\"\356\001\n\016ContactDetai" + - "ls\022\016\n\006number\030\001 \001(\t\022\014\n\004name\030\002 \001(\t\0224\n\006avat" + - "ar\030\003 \001(\0132$.signalservice.ContactDetails." + - "Avatar\022\r\n\005color\030\004 \001(\t\022\022\n\nprofileKey\030\006 \001(" + - "\014\022\017\n\007blocked\030\007 \001(\010\022\023\n\013expireTimer\030\010 \001(\r\022" + - "\020\n\010nickname\030e \001(\t\032-\n\006Avatar\022\023\n\013contentTy" + - "pe\030\001 \001(\t\022\016\n\006length\030\002 \001(\r\"\367\001\n\014GroupDetail", - "s\022\n\n\002id\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\022\017\n\007members\030\003" + - " \003(\t\0222\n\006avatar\030\004 \001(\0132\".signalservice.Gro" + - "upDetails.Avatar\022\024\n\006active\030\005 \001(\010:\004true\022\023" + - "\n\013expireTimer\030\006 \001(\r\022\r\n\005color\030\007 \001(\t\022\017\n\007bl" + - "ocked\030\010 \001(\010\022\016\n\006admins\030\t \003(\t\032-\n\006Avatar\022\023\n" + - "\013contentType\030\001 \001(\t\022\016\n\006length\030\002 \001(\r\"\"\n\016Pu" + - "blicChatInfo\022\020\n\010serverID\030\001 \001(\004BB\n+org.se" + - "ssion.libsignal.service.internal.pushB\023S" + - "ignalServiceProtos" + "gurationMessage\022M\n\032dataExtractionNotific" + + "ation\030R \001(\0132).signalservice.DataExtracti" + + "onNotification\"U\n#ClosedGroupCiphertextM" + + "essageWrapper\022\022\n\nciphertext\030\001 \001(\014\022\032\n\022eph" + + "emeralPublicKey\030\002 \001(\014\"0\n\007KeyPair\022\021\n\tpubl", + "icKey\030\001 \002(\014\022\022\n\nprivateKey\030\002 \002(\014\"\226\001\n\032Data" + + "ExtractionNotification\022<\n\004type\030\001 \002(\0162..s" + + "ignalservice.DataExtractionNotification." + + "Type\022\021\n\ttimestamp\030\002 \001(\004\"\'\n\004Type\022\016\n\nSCREE" + + "NSHOT\020\001\022\017\n\013MEDIA_SAVED\020\002\"\357\024\n\013DataMessage" + + "\022\014\n\004body\030\001 \001(\t\0225\n\013attachments\030\002 \003(\0132 .si" + + "gnalservice.AttachmentPointer\022*\n\005group\030\003" + + " \001(\0132\033.signalservice.GroupContext\022\r\n\005fla" + + "gs\030\004 \001(\r\022\023\n\013expireTimer\030\005 \001(\r\022\022\n\nprofile" + + "Key\030\006 \001(\014\022\021\n\ttimestamp\030\007 \001(\004\022/\n\005quote\030\010 ", + "\001(\0132 .signalservice.DataMessage.Quote\0223\n" + + "\007contact\030\t \003(\0132\".signalservice.DataMessa" + + "ge.Contact\0223\n\007preview\030\n \003(\0132\".signalserv" + + "ice.DataMessage.Preview\0227\n\007profile\030e \001(\013" + + "2&.signalservice.DataMessage.LokiProfile" + + "\022W\n\031closedGroupControlMessage\030h \001(\01324.si" + + "gnalservice.DataMessage.ClosedGroupContr" + + "olMessage\022\022\n\nsyncTarget\030i \001(\t\0226\n\016publicC" + + "hatInfo\030\347\007 \001(\0132\035.signalservice.PublicCha" + + "tInfo\032\225\002\n\005Quote\022\n\n\002id\030\001 \001(\004\022\016\n\006author\030\002 ", + "\001(\t\022\014\n\004text\030\003 \001(\t\022F\n\013attachments\030\004 \003(\01321" + + ".signalservice.DataMessage.Quote.QuotedA" + + "ttachment\032\231\001\n\020QuotedAttachment\022\023\n\013conten" + + "tType\030\001 \001(\t\022\020\n\010fileName\030\002 \001(\t\0223\n\tthumbna" + + "il\030\003 \001(\0132 .signalservice.AttachmentPoint" + + "er\022\r\n\005flags\030\004 \001(\r\"\032\n\005Flags\022\021\n\rVOICE_MESS" + + "AGE\020\001\032\304\010\n\007Contact\0225\n\004name\030\001 \001(\0132\'.signal" + + "service.DataMessage.Contact.Name\0228\n\006numb" + + "er\030\003 \003(\0132(.signalservice.DataMessage.Con" + + "tact.Phone\0227\n\005email\030\004 \003(\0132(.signalservic", + "e.DataMessage.Contact.Email\022A\n\007address\030\005" + + " \003(\01320.signalservice.DataMessage.Contact" + + ".PostalAddress\0229\n\006avatar\030\006 \001(\0132).signals" + + "ervice.DataMessage.Contact.Avatar\022\024\n\014org" + + "anization\030\007 \001(\t\032v\n\004Name\022\021\n\tgivenName\030\001 \001" + + "(\t\022\022\n\nfamilyName\030\002 \001(\t\022\016\n\006prefix\030\003 \001(\t\022\016" + + "\n\006suffix\030\004 \001(\t\022\022\n\nmiddleName\030\005 \001(\t\022\023\n\013di" + + "splayName\030\006 \001(\t\032\226\001\n\005Phone\022\r\n\005value\030\001 \001(\t" + + "\022;\n\004type\030\002 \001(\0162-.signalservice.DataMessa" + + "ge.Contact.Phone.Type\022\r\n\005label\030\003 \001(\t\"2\n\004", + "Type\022\010\n\004HOME\020\001\022\n\n\006MOBILE\020\002\022\010\n\004WORK\020\003\022\n\n\006" + + "CUSTOM\020\004\032\226\001\n\005Email\022\r\n\005value\030\001 \001(\t\022;\n\004typ" + + "e\030\002 \001(\0162-.signalservice.DataMessage.Cont" + + "act.Email.Type\022\r\n\005label\030\003 \001(\t\"2\n\004Type\022\010\n" + + "\004HOME\020\001\022\n\n\006MOBILE\020\002\022\010\n\004WORK\020\003\022\n\n\006CUSTOM\020" + + "\004\032\201\002\n\rPostalAddress\022C\n\004type\030\001 \001(\01625.sign" + + "alservice.DataMessage.Contact.PostalAddr" + + "ess.Type\022\r\n\005label\030\002 \001(\t\022\016\n\006street\030\003 \001(\t\022" + + "\r\n\005pobox\030\004 \001(\t\022\024\n\014neighborhood\030\005 \001(\t\022\014\n\004" + + "city\030\006 \001(\t\022\016\n\006region\030\007 \001(\t\022\020\n\010postcode\030\010", + " \001(\t\022\017\n\007country\030\t \001(\t\"&\n\004Type\022\010\n\004HOME\020\001\022" + + "\010\n\004WORK\020\002\022\n\n\006CUSTOM\020\003\032M\n\006Avatar\0220\n\006avata" + + "r\030\001 \001(\0132 .signalservice.AttachmentPointe" + + "r\022\021\n\tisProfile\030\002 \001(\010\032V\n\007Preview\022\013\n\003url\030\001" + + " \001(\t\022\r\n\005title\030\002 \001(\t\022/\n\005image\030\003 \001(\0132 .sig" + + "nalservice.AttachmentPointer\032:\n\013LokiProf" + + "ile\022\023\n\013displayName\030\001 \001(\t\022\026\n\016profilePictu" + + "re\030\002 \001(\t\032\221\004\n\031ClosedGroupControlMessage\022G" + + "\n\004type\030\001 \002(\01629.signalservice.DataMessage" + + ".ClosedGroupControlMessage.Type\022\021\n\tpubli", + "cKey\030\002 \001(\014\022\014\n\004name\030\003 \001(\t\0221\n\021encryptionKe" + + "yPair\030\004 \001(\0132\026.signalservice.KeyPair\022\017\n\007m" + + "embers\030\005 \003(\014\022\016\n\006admins\030\006 \003(\014\022U\n\010wrappers" + + "\030\007 \003(\0132C.signalservice.DataMessage.Close" + + "dGroupControlMessage.KeyPairWrapper\032=\n\016K" + + "eyPairWrapper\022\021\n\tpublicKey\030\001 \002(\014\022\030\n\020encr" + + "yptedKeyPair\030\002 \002(\014\"\237\001\n\004Type\022\007\n\003NEW\020\001\022\n\n\006" + + "UPDATE\020\002\022\027\n\023ENCRYPTION_KEY_PAIR\020\003\022\017\n\013NAM" + + "E_CHANGE\020\004\022\021\n\rMEMBERS_ADDED\020\005\022\023\n\017MEMBERS" + + "_REMOVED\020\006\022\017\n\013MEMBER_LEFT\020\007\022\037\n\033ENCRYPTIO", + "N_KEY_PAIR_REQUEST\020\010\"$\n\005Flags\022\033\n\027EXPIRAT" + + "ION_TIMER_UPDATE\020\002\"\316\003\n\024ConfigurationMess" + + "age\022E\n\014closedGroups\030\001 \003(\0132/.signalservic" + + "e.ConfigurationMessage.ClosedGroup\022\022\n\nop" + + "enGroups\030\002 \003(\t\022\023\n\013displayName\030\003 \001(\t\022\026\n\016p" + + "rofilePicture\030\004 \001(\t\022\022\n\nprofileKey\030\005 \001(\014\022" + + "=\n\010contacts\030\006 \003(\0132+.signalservice.Config" + + "urationMessage.Contact\032\202\001\n\013ClosedGroup\022\021" + + "\n\tpublicKey\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\0221\n\021encry" + + "ptionKeyPair\030\003 \001(\0132\026.signalservice.KeyPa", + "ir\022\017\n\007members\030\004 \003(\014\022\016\n\006admins\030\005 \003(\014\032V\n\007C" + + "ontact\022\021\n\tpublicKey\030\001 \002(\014\022\014\n\004name\030\002 \002(\t\022" + + "\026\n\016profilePicture\030\003 \001(\t\022\022\n\nprofileKey\030\004 " + + "\001(\014\"u\n\016ReceiptMessage\0220\n\004type\030\001 \001(\0162\".si" + + "gnalservice.ReceiptMessage.Type\022\021\n\ttimes" + + "tamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010DELIVERY\020\000\022\010\n\004READ" + + "\020\001\"\354\001\n\021AttachmentPointer\022\n\n\002id\030\001 \001(\006\022\023\n\013" + + "contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004size\030\004" + + " \001(\r\022\021\n\tthumbnail\030\005 \001(\014\022\016\n\006digest\030\006 \001(\014\022" + + "\020\n\010fileName\030\007 \001(\t\022\r\n\005flags\030\010 \001(\r\022\r\n\005widt", + "h\030\t \001(\r\022\016\n\006height\030\n \001(\r\022\017\n\007caption\030\013 \001(\t" + + "\022\013\n\003url\030e \001(\t\"\032\n\005Flags\022\021\n\rVOICE_MESSAGE\020" + + "\001\"\243\002\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022.\n\004type\030\002" + + " \001(\0162 .signalservice.GroupContext.Type\022\014" + + "\n\004name\030\003 \001(\t\022\017\n\007members\030\004 \003(\t\0220\n\006avatar\030" + + "\005 \001(\0132 .signalservice.AttachmentPointer\022" + + "\016\n\006admins\030\006 \003(\t\022\023\n\nnewMembers\030\346\007 \003(\t\022\027\n\016" + + "removedMembers\030\347\007 \003(\t\"H\n\004Type\022\013\n\007UNKNOWN" + + "\020\000\022\n\n\006UPDATE\020\001\022\013\n\007DELIVER\020\002\022\010\n\004QUIT\020\003\022\020\n" + + "\014REQUEST_INFO\020\004\"\356\001\n\016ContactDetails\022\016\n\006nu", + "mber\030\001 \001(\t\022\014\n\004name\030\002 \001(\t\0224\n\006avatar\030\003 \001(\013" + + "2$.signalservice.ContactDetails.Avatar\022\r" + + "\n\005color\030\004 \001(\t\022\022\n\nprofileKey\030\006 \001(\014\022\017\n\007blo" + + "cked\030\007 \001(\010\022\023\n\013expireTimer\030\010 \001(\r\022\020\n\010nickn" + + "ame\030e \001(\t\032-\n\006Avatar\022\023\n\013contentType\030\001 \001(\t" + + "\022\016\n\006length\030\002 \001(\r\"\367\001\n\014GroupDetails\022\n\n\002id\030" + + "\001 \001(\014\022\014\n\004name\030\002 \001(\t\022\017\n\007members\030\003 \003(\t\0222\n\006" + + "avatar\030\004 \001(\0132\".signalservice.GroupDetail" + + "s.Avatar\022\024\n\006active\030\005 \001(\010:\004true\022\023\n\013expire" + + "Timer\030\006 \001(\r\022\r\n\005color\030\007 \001(\t\022\017\n\007blocked\030\010 ", + "\001(\010\022\016\n\006admins\030\t \003(\t\032-\n\006Avatar\022\023\n\013content" + + "Type\030\001 \001(\t\022\016\n\006length\030\002 \001(\r\"\"\n\016PublicChat" + + "Info\022\020\n\010serverID\030\001 \001(\004BB\n+org.session.li" + + "bsignal.service.internal.pushB\023SignalSer" + + "viceProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -32679,7 +33510,7 @@ public final class SignalServiceProtos { internal_static_signalservice_Content_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_Content_descriptor, - new java.lang.String[] { "DataMessage", "ReceiptMessage", "TypingMessage", "ConfigurationMessage", }); + new java.lang.String[] { "DataMessage", "ReceiptMessage", "TypingMessage", "ConfigurationMessage", "DataExtractionNotification", }); internal_static_signalservice_ClosedGroupCiphertextMessageWrapper_descriptor = getDescriptor().getMessageTypes().get(3); internal_static_signalservice_ClosedGroupCiphertextMessageWrapper_fieldAccessorTable = new @@ -32692,8 +33523,14 @@ public final class SignalServiceProtos { com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_KeyPair_descriptor, new java.lang.String[] { "PublicKey", "PrivateKey", }); - internal_static_signalservice_DataMessage_descriptor = + internal_static_signalservice_DataExtractionNotification_descriptor = getDescriptor().getMessageTypes().get(5); + internal_static_signalservice_DataExtractionNotification_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_signalservice_DataExtractionNotification_descriptor, + new java.lang.String[] { "Type", "Timestamp", }); + internal_static_signalservice_DataMessage_descriptor = + getDescriptor().getMessageTypes().get(6); internal_static_signalservice_DataMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_descriptor, @@ -32771,7 +33608,7 @@ public final class SignalServiceProtos { internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_descriptor, new java.lang.String[] { "PublicKey", "EncryptedKeyPair", }); internal_static_signalservice_ConfigurationMessage_descriptor = - getDescriptor().getMessageTypes().get(6); + getDescriptor().getMessageTypes().get(7); internal_static_signalservice_ConfigurationMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_ConfigurationMessage_descriptor, @@ -32789,25 +33626,25 @@ public final class SignalServiceProtos { internal_static_signalservice_ConfigurationMessage_Contact_descriptor, new java.lang.String[] { "PublicKey", "Name", "ProfilePicture", "ProfileKey", }); internal_static_signalservice_ReceiptMessage_descriptor = - getDescriptor().getMessageTypes().get(7); + getDescriptor().getMessageTypes().get(8); internal_static_signalservice_ReceiptMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_ReceiptMessage_descriptor, new java.lang.String[] { "Type", "Timestamp", }); internal_static_signalservice_AttachmentPointer_descriptor = - getDescriptor().getMessageTypes().get(8); + getDescriptor().getMessageTypes().get(9); internal_static_signalservice_AttachmentPointer_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_AttachmentPointer_descriptor, new java.lang.String[] { "Id", "ContentType", "Key", "Size", "Thumbnail", "Digest", "FileName", "Flags", "Width", "Height", "Caption", "Url", }); internal_static_signalservice_GroupContext_descriptor = - getDescriptor().getMessageTypes().get(9); + getDescriptor().getMessageTypes().get(10); internal_static_signalservice_GroupContext_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_GroupContext_descriptor, new java.lang.String[] { "Id", "Type", "Name", "Members", "Avatar", "Admins", "NewMembers", "RemovedMembers", }); internal_static_signalservice_ContactDetails_descriptor = - getDescriptor().getMessageTypes().get(10); + getDescriptor().getMessageTypes().get(11); internal_static_signalservice_ContactDetails_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_ContactDetails_descriptor, @@ -32819,7 +33656,7 @@ public final class SignalServiceProtos { internal_static_signalservice_ContactDetails_Avatar_descriptor, new java.lang.String[] { "ContentType", "Length", }); internal_static_signalservice_GroupDetails_descriptor = - getDescriptor().getMessageTypes().get(11); + getDescriptor().getMessageTypes().get(12); internal_static_signalservice_GroupDetails_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_GroupDetails_descriptor, @@ -32831,7 +33668,7 @@ public final class SignalServiceProtos { internal_static_signalservice_GroupDetails_Avatar_descriptor, new java.lang.String[] { "ContentType", "Length", }); internal_static_signalservice_PublicChatInfo_descriptor = - getDescriptor().getMessageTypes().get(12); + getDescriptor().getMessageTypes().get(13); internal_static_signalservice_PublicChatInfo_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_PublicChatInfo_descriptor, From ca7202f255ab5b2c89fcb6656be7e6ed2cbe9ddd Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 9 Mar 2021 17:26:29 +1100 Subject: [PATCH 02/51] feat: add new message receive pipeline in important places and fix parsing issues --- .../securesms/ApplicationContext.java | 48 ++++++++----------- .../OptimizedMessageNotifier.java | 11 ++--- .../control/ClosedGroupControlMessage.kt | 5 +- .../messages/control/ExpirationTimerUpdate.kt | 4 +- .../messaging/messages/control/ReadReceipt.kt | 4 +- .../messages/control/TypingIndicator.kt | 4 +- .../messages/visible/VisibleMessage.kt | 8 ++-- .../sending_receiving/MessageReceiver.kt | 18 +------ .../MessageReceiverHandler.kt | 8 ++-- .../MessageSenderClosedGroup.kt | 8 ++-- .../sending_receiving/pollers/Poller.kt | 7 +-- 11 files changed, 47 insertions(+), 78 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index b9051c0bef..e1d6b702a5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -34,23 +34,28 @@ import com.google.firebase.iid.FirebaseInstanceId; import org.conscrypt.Conscrypt; import org.session.libsession.messaging.MessagingConfiguration; import org.session.libsession.messaging.avatars.AvatarHelper; +import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; +import org.session.libsession.messaging.sending_receiving.pollers.Poller; +import org.session.libsession.messaging.threads.Address; import org.session.libsession.snode.SnodeConfiguration; import org.session.libsession.utilities.SSKEnvironment; -import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; -import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; - +import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper; import org.session.libsession.utilities.dynamiclanguage.LocaleParser; +import org.session.libsession.utilities.preferences.ProfileKeyUtil; +import org.session.libsignal.service.api.util.StreamDetails; +import org.session.libsignal.service.loki.api.PushNotificationAPI; +import org.session.libsignal.service.loki.api.SnodeAPI; +import org.session.libsignal.service.loki.api.SwarmAPI; +import org.session.libsignal.service.loki.api.fileserver.FileServerAPI; +import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI; +import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol; +import org.session.libsignal.service.loki.utilities.mentions.MentionsManager; +import org.session.libsignal.utilities.logging.Log; import org.signal.aesgcmprovider.AesGcmProvider; -import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl; -import org.thoughtcrime.securesms.sskenvironment.ProfileManager; -import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager; -import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository; import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.session.libsession.utilities.preferences.ProfileKeyUtil; -import org.session.libsession.messaging.threads.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.dependencies.InjectableType; @@ -60,9 +65,7 @@ import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer; import org.thoughtcrime.securesms.jobs.FastJobStorage; import org.thoughtcrime.securesms.jobs.JobManagerFactories; -import org.thoughtcrime.securesms.jobs.PushContentReceiveJob; import org.thoughtcrime.securesms.logging.AndroidLogger; -import org.session.libsignal.utilities.logging.Log; import org.thoughtcrime.securesms.logging.PersistentLogger; import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger; import org.thoughtcrime.securesms.loki.activities.HomeActivity; @@ -70,6 +73,7 @@ import org.thoughtcrime.securesms.loki.api.BackgroundPollWorker; import org.thoughtcrime.securesms.loki.api.ClosedGroupPoller; import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager; import org.thoughtcrime.securesms.loki.api.PublicChatManager; +import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl; import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; @@ -84,22 +88,14 @@ import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.LocalBackupListener; import org.thoughtcrime.securesms.service.UpdateApkRefreshListener; +import org.thoughtcrime.securesms.sskenvironment.ProfileManager; +import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager; +import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository; import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper; import org.webrtc.PeerConnectionFactory; import org.webrtc.PeerConnectionFactory.InitializationOptions; import org.webrtc.voiceengine.WebRtcAudioManager; import org.webrtc.voiceengine.WebRtcAudioUtils; -import org.session.libsignal.service.api.messages.SignalServiceEnvelope; -import org.session.libsignal.service.api.util.StreamDetails; -import org.session.libsignal.service.internal.push.SignalServiceProtos; -import org.session.libsignal.service.loki.api.Poller; -import org.session.libsignal.service.loki.api.PushNotificationAPI; -import org.session.libsignal.service.loki.api.SnodeAPI; -import org.session.libsignal.service.loki.api.SwarmAPI; -import org.session.libsignal.service.loki.api.fileserver.FileServerAPI; -import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI; -import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol; -import org.session.libsignal.service.loki.utilities.mentions.MentionsManager; import java.io.File; import java.io.FileInputStream; @@ -456,15 +452,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc return; } LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this); - Context context = this; SwarmAPI.Companion.configureIfNeeded(apiDB); SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster); - poller = new Poller(userPublicKey, apiDB, envelopes -> { - for (SignalServiceProtos.Envelope envelope : envelopes) { - new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(envelope), false); - } - return Unit.INSTANCE; - }); + poller = new Poller(); ClosedGroupPoller.Companion.configureIfNeeded(this); closedGroupPoller = ClosedGroupPoller.Companion.getShared(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java index c994ac4e00..02250be314 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java @@ -6,14 +6,13 @@ import android.os.Looper; import androidx.annotation.MainThread; import androidx.annotation.NonNull; +import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; +import org.session.libsession.messaging.sending_receiving.pollers.Poller; +import org.session.libsession.messaging.threads.recipients.Recipient; +import org.session.libsession.utilities.Debouncer; +import org.session.libsignal.utilities.ThreadUtils; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.loki.api.PublicChatManager; -import org.session.libsession.utilities.Debouncer; -import org.session.libsignal.service.loki.api.Poller; -import org.session.libsignal.utilities.ThreadUtils; - -import org.session.libsession.messaging.threads.recipients.Recipient; -import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import java.util.concurrent.TimeUnit; diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt index 4affd25f2f..14ecbf4f0f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt @@ -4,11 +4,11 @@ import com.google.protobuf.ByteString import org.session.libsignal.libsignal.ecc.DjbECPrivateKey import org.session.libsignal.libsignal.ecc.DjbECPublicKey import org.session.libsignal.libsignal.ecc.ECKeyPair -import org.session.libsignal.utilities.logging.Log import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.utilities.Hex +import org.session.libsignal.utilities.logging.Log class ClosedGroupControlMessage() : ControlMessage() { @@ -56,7 +56,8 @@ class ClosedGroupControlMessage() : ControlMessage() { const val TAG = "ClosedGroupControlMessage" fun fromProto(proto: SignalServiceProtos.Content): ClosedGroupControlMessage? { - val closedGroupControlMessageProto = proto.dataMessage?.closedGroupControlMessage ?: return null + if (!proto.hasDataMessage() || !proto.dataMessage.hasClosedGroupControlMessage()) return null + val closedGroupControlMessageProto = proto.dataMessage?.closedGroupControlMessage!! val kind: Kind when(closedGroupControlMessageProto.type) { DataMessage.ClosedGroupControlMessage.Type.NEW -> { diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt index a1c8e6b775..de587f8096 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt @@ -1,7 +1,7 @@ package org.session.libsession.messaging.messages.control -import org.session.libsignal.utilities.logging.Log import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.session.libsignal.utilities.logging.Log class ExpirationTimerUpdate() : ControlMessage() { @@ -12,7 +12,7 @@ class ExpirationTimerUpdate() : ControlMessage() { const val TAG = "ExpirationTimerUpdate" fun fromProto(proto: SignalServiceProtos.Content): ExpirationTimerUpdate? { - val dataMessageProto = proto.dataMessage ?: return null + val dataMessageProto = if (proto.hasDataMessage()) proto.dataMessage else return null val isExpirationTimerUpdate = dataMessageProto.flags.and(SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE) != 0 if (!isExpirationTimerUpdate) return null val duration = dataMessageProto.expireTimer diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt index 96aee21ea4..59337dd277 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt @@ -1,7 +1,7 @@ package org.session.libsession.messaging.messages.control -import org.session.libsignal.utilities.logging.Log import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.session.libsignal.utilities.logging.Log class ReadReceipt() : ControlMessage() { @@ -11,7 +11,7 @@ class ReadReceipt() : ControlMessage() { const val TAG = "ReadReceipt" fun fromProto(proto: SignalServiceProtos.Content): ReadReceipt? { - val receiptProto = proto.receiptMessage ?: return null + val receiptProto = if (proto.hasReceiptMessage()) proto.receiptMessage else return null if (receiptProto.type != SignalServiceProtos.ReceiptMessage.Type.READ) return null val timestamps = receiptProto.timestampList if (timestamps.isEmpty()) return null diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/TypingIndicator.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/TypingIndicator.kt index 10192b1d3e..8bbfd727bb 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/TypingIndicator.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/TypingIndicator.kt @@ -1,7 +1,7 @@ package org.session.libsession.messaging.messages.control -import org.session.libsignal.utilities.logging.Log import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.session.libsignal.utilities.logging.Log class TypingIndicator() : ControlMessage() { @@ -11,7 +11,7 @@ class TypingIndicator() : ControlMessage() { const val TAG = "TypingIndicator" fun fromProto(proto: SignalServiceProtos.Content): TypingIndicator? { - val typingIndicatorProto = proto.typingMessage ?: return null + val typingIndicatorProto = if (proto.hasTypingMessage()) proto.typingMessage else return null val kind = Kind.fromProto(typingIndicatorProto.action) return TypingIndicator(kind = kind) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt index d353886eec..96452cd58a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt @@ -12,7 +12,7 @@ class VisibleMessage : Message() { var syncTarget: String? = null var text: String? = null - var attachmentIDs = ArrayList() + var attachmentIDs:List = mutableListOf() var quote: Quote? = null var linkPreview: LinkPreview? = null var contact: Contact? = null @@ -24,17 +24,17 @@ class VisibleMessage : Message() { const val TAG = "VisibleMessage" fun fromProto(proto: SignalServiceProtos.Content): VisibleMessage? { - val dataMessage = proto.dataMessage ?: return null + val dataMessage = if (proto.hasDataMessage()) proto.dataMessage else return null val result = VisibleMessage() result.syncTarget = dataMessage.syncTarget result.text = dataMessage.body // Attachments are handled in MessageReceiver - val quoteProto = dataMessage.quote + val quoteProto = if (dataMessage.hasQuote()) dataMessage.quote else null quoteProto?.let { val quote = Quote.fromProto(quoteProto) quote?.let { result.quote = quote } } - val linkPreviewProto = dataMessage.previewList.first() + val linkPreviewProto = dataMessage.previewList.firstOrNull() linkPreviewProto?.let { val linkPreview = LinkPreview.fromProto(linkPreviewProto) linkPreview?.let { result.linkPreview = linkPreview } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index 5b661327cb..af0f169ba5 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -4,7 +4,7 @@ import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.* import org.session.libsession.messaging.messages.visible.VisibleMessage - +import org.session.libsignal.service.internal.push.PushTransportDetails import org.session.libsignal.service.internal.push.SignalServiceProtos object MessageReceiver { @@ -94,20 +94,6 @@ object MessageReceiver { } groupPublicKey = envelope.source decrypt() -// try { -// decrypt() -// } catch(error: Exception) { -// val now = System.currentTimeMillis() -// var shouldRequestEncryptionKeyPair = true -// lastEncryptionKeyPairRequest[groupPublicKey!!]?.let { -// shouldRequestEncryptionKeyPair = now - it > 30 * 1000 -// } -// if (shouldRequestEncryptionKeyPair) { -// MessageSender.requestEncryptionKeyPair(groupPublicKey) -// lastEncryptionKeyPairRequest[groupPublicKey] = now -// } -// throw error -// } } else -> throw Error.UnknownEnvelopeType } @@ -115,7 +101,7 @@ object MessageReceiver { // Don't process the envelope any further if the sender is blocked if (isBlock(sender!!)) throw Error.SenderBlocked // Parse the proto - val proto = SignalServiceProtos.Content.parseFrom(plaintext) + val proto = SignalServiceProtos.Content.parseFrom(PushTransportDetails.getStrippedPaddingMessageBody(plaintext)) // Parse the message val message: Message = ReadReceipt.fromProto(proto) ?: TypingIndicator.fromProto(proto) ?: diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index fa94b00825..bc61dde34d 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -21,15 +21,14 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.libsignal.ecc.DjbECPrivateKey import org.session.libsignal.libsignal.ecc.DjbECPublicKey import org.session.libsignal.libsignal.ecc.ECKeyPair -import org.session.libsignal.utilities.logging.Log import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.toHexString +import org.session.libsignal.utilities.logging.Log import java.security.MessageDigest import java.util.* -import kotlin.collections.ArrayList internal fun MessageReceiver.isBlock(publicKey: String): Boolean { val context = MessagingConfiguration.shared.context @@ -134,8 +133,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS } } val attachmentIDs = storage.persistAttachments(message.id ?: 0, attachments) - message.attachmentIDs = attachmentIDs as ArrayList - var attachmentsToDownload = attachmentIDs + message.attachmentIDs = attachmentIDs.toMutableList() // Update profile if needed val newProfile = message.profile if (newProfile != null) { @@ -194,7 +192,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS val messageID = storage.persist(message, quoteModel, linkPreviews, message.groupPublicKey, openGroupID) ?: throw MessageReceiver.Error.NoThread message.threadID = threadID // Start attachment downloads if needed - attachmentsToDownload.forEach { attachmentID -> + attachmentIDs.forEach { attachmentID -> val downloadJob = AttachmentDownloadJob(attachmentID, messageID) JobQueue.shared.add(downloadJob) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt index 473dd1f9ec..8f49045ed8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt @@ -5,23 +5,20 @@ package org.session.libsession.messaging.sending_receiving import com.google.protobuf.ByteString import nl.komponents.kovenant.Promise import nl.komponents.kovenant.deferred - import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage -import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.messaging.sending_receiving.MessageSender.Error +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.messaging.threads.Address -import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.Hex - import org.session.libsignal.libsignal.ecc.Curve import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded +import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.logging.Log import java.util.* @@ -252,6 +249,7 @@ fun MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey: String, ta sendNonDurably(closedGroupControlMessage, Address.fromSerialized(groupID)).success { // Store it * after * having sent out the message to the group storage.addClosedGroupEncryptionKeyPair(newKeyPair, groupPublicKey) + }.always { pendingKeyPair[groupPublicKey] = Optional.absent() } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt index cdc42be968..474a2768b5 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt @@ -2,25 +2,22 @@ package org.session.libsession.messaging.sending_receiving.pollers import nl.komponents.kovenant.* import nl.komponents.kovenant.functional.bind - import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveJob import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeConfiguration - import org.session.libsignal.service.loki.api.Snode -import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.Base64 - +import org.session.libsignal.utilities.logging.Log import java.security.SecureRandom import java.util.* private class PromiseCanceledException : Exception("Promise canceled.") class Poller { - private val userPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey() ?: "" + var userPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey() ?: "" private var hasStarted: Boolean = false private val usedSnodes: MutableSet = mutableSetOf() public var isCaughtUp = false From 323fb751490db5442a1db20edb2d36678f62c45f Mon Sep 17 00:00:00 2001 From: jubb Date: Fri, 12 Mar 2021 17:15:33 +1100 Subject: [PATCH 03/51] fix: adding some message receive functionality --- .../attachments/DatabaseAttachmentProvider.kt | 8 ++- .../securesms/database/Storage.kt | 14 +++-- .../database/MessageDataProvider.kt | 2 +- .../messaging/jobs/AttachmentDownloadJob.kt | 16 ++++-- .../messaging/jobs/AttachmentUploadJob.kt | 56 +++++++++---------- .../libsession/messaging/jobs/JobQueue.kt | 38 +++++++++---- .../messaging/jobs/MessageReceiveJob.kt | 6 +- .../messaging/messages/visible/Attachment.kt | 6 +- .../messages/visible/VisibleMessage.kt | 4 +- .../messaging/opengroups/OpenGroupMessage.kt | 6 +- .../sending_receiving/MessageReceiver.kt | 2 +- .../MessageReceiverHandler.kt | 2 +- 12 files changed, 93 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt index b65366f94c..ef46dccdd8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -94,9 +94,9 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) return message.linkPreviews.firstOrNull()?.attachmentId?.rowId } - override fun insertAttachment(messageId: Long, attachmentId: Long, stream: InputStream) { + override fun insertAttachment(messageId: Long, attachmentId: AttachmentId, stream: InputStream) { val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context) - attachmentDatabase.insertAttachmentsForPlaceholder(messageId, AttachmentId(attachmentId, 0), stream) + attachmentDatabase.insertAttachmentsForPlaceholder(messageId, attachmentId, stream) } override fun isOutgoingMessage(timestamp: Long): Boolean { @@ -191,6 +191,10 @@ fun DatabaseAttachment.toAttachmentPointer(): SessionServiceAttachmentPointer { return SessionServiceAttachmentPointer(attachmentId.rowId, contentType, key?.toByteArray(), Optional.fromNullable(size.toInt()), Optional.absent(), width, height, Optional.fromNullable(digest), Optional.fromNullable(fileName), isVoiceNote, Optional.fromNullable(caption), url) } +fun SessionServiceAttachmentPointer.toSignalPointer(): SignalServiceAttachmentPointer { + return SignalServiceAttachmentPointer(id,contentType,key?.toByteArray() ?: byteArrayOf(), size, preview, width, height, digest, fileName, voiceNote, caption, url) +} + fun DatabaseAttachment.toAttachmentStream(context: Context): SessionServiceAttachmentStream { val stream = PartAuthority.getAttachmentStream(context, this.dataUri!!) val listener = SignalServiceAttachment.ProgressListener { total: Long, progress: Long -> EventBus.getDefault().postSticky(PartProgressEvent(this, total, progress))} 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 d4096b1083..853fc3debe 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,9 @@ import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageSendJob +import org.session.libsession.messaging.messages.signal.IncomingGroupMessage +import org.session.libsession.messaging.messages.signal.IncomingTextMessage +import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.opengroups.OpenGroup @@ -20,6 +23,7 @@ import org.session.libsession.messaging.threads.GroupRecord import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.preferences.ProfileKeyUtil import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.util.KeyHelper import org.session.libsignal.libsignal.util.guava.Optional @@ -29,6 +33,7 @@ import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.loki.api.opengroups.PublicChat import org.session.libsignal.utilities.logging.Log +import org.thoughtcrime.securesms.attachments.toSignalPointer import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase @@ -40,10 +45,6 @@ import org.thoughtcrime.securesms.mms.IncomingMediaMessage import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage import org.thoughtcrime.securesms.mms.OutgoingMediaMessage import org.thoughtcrime.securesms.mms.PartAuthority -import org.session.libsession.messaging.messages.signal.IncomingGroupMessage -import org.session.libsession.messaging.messages.signal.IncomingTextMessage -import org.session.libsession.messaging.messages.signal.OutgoingTextMessage -import org.session.libsession.utilities.preferences.ProfileKeyUtil class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { override fun getUserPublicKey(): String? { @@ -107,7 +108,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, 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 mmsDatabase = DatabaseFactory.getMmsDatabase(context) - mmsDatabase.beginTransaction() val insertResult = if (message.sender == getUserPublicKey()) { val targetAddress = if (message.syncTarget != null) { Address.fromSerialized(message.syncTarget!!) @@ -125,13 +125,15 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, PointerAttachment.forPointer(Optional.of(it)).orNull() } val mediaMessage = OutgoingMediaMessage.from(message, Recipient.from(context, targetAddress, false), attachments, quote.orNull(), linkPreviews.orNull().firstOrNull()) + mmsDatabase.beginTransaction() mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) } else { // It seems like we have replaced SignalServiceAttachment with SessionServiceAttachment val attachments: Optional> = Optional.of(message.attachmentIDs.mapNotNull { - DatabaseFactory.getAttachmentProvider(context).getSignalAttachmentPointer(it) + DatabaseFactory.getAttachmentProvider(context).getAttachmentPointer(it)?.toSignalPointer() }) val mediaMessage = IncomingMediaMessage.from(message, senderAddress, senderRecipient.expireMessages * 1000L, group, attachments, quote, linkPreviews) + mmsDatabase.beginTransaction() if (group.isPresent) { mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) } else { diff --git a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt index 5d925b82c9..d15274dfcb 100644 --- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -23,7 +23,7 @@ interface MessageDataProvider { fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long) - fun insertAttachment(messageId: Long, attachmentId: Long, stream : InputStream) + fun insertAttachment(messageId: Long, attachmentId: AttachmentId, stream : InputStream) fun isOutgoingMessage(timestamp: Long): Boolean diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index 37eafe8b1b..811f355cd4 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -5,6 +5,8 @@ import org.session.libsession.messaging.fileserver.FileServerAPI import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState import org.session.libsession.messaging.utilities.DotNetAPI import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream +import org.session.libsignal.utilities.Base64 +import org.session.libsignal.utilities.logging.Log import java.io.File import java.io.FileInputStream @@ -33,7 +35,8 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) override fun execute() { val messageDataProvider = MessagingConfiguration.shared.messageDataProvider - val attachmentStream = messageDataProvider.getAttachmentStream(attachmentID) ?: return handleFailure(Error.NoAttachment) + messageDataProvider.getDatabaseAttachment(attachmentID) + val attachment = messageDataProvider.getDatabaseAttachment(attachmentID) ?: return handleFailure(Error.NoAttachment) messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID) val tempFile = createTempFile() val handleFailure: (java.lang.Exception) -> Unit = { exception -> @@ -51,7 +54,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) } } try { - FileServerAPI.shared.downloadFile(tempFile, attachmentStream.url, MAX_ATTACHMENT_SIZE, attachmentStream.listener) + FileServerAPI.shared.downloadFile(tempFile, attachment.url, MAX_ATTACHMENT_SIZE, null) } catch (e: Exception) { return handleFailure(e) } @@ -59,16 +62,17 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) // DECRYPTION // Assume we're retrieving an attachment for an open group server if the digest is not set - var stream = if (!attachmentStream.digest.isPresent || attachmentStream.key == null) FileInputStream(tempFile) - else AttachmentCipherInputStream.createForAttachment(tempFile, attachmentStream.length.or(0).toLong(), attachmentStream.key?.toByteArray(), attachmentStream?.digest.get()) + val stream = if (attachment.digest == null || attachment.key == null) FileInputStream(tempFile) + else AttachmentCipherInputStream.createForAttachment(tempFile, attachment.size, Base64.decode(attachment.key), attachment.digest) - messageDataProvider.insertAttachment(databaseMessageID, attachmentID, stream) + messageDataProvider.insertAttachment(databaseMessageID, attachment.attachmentId, stream) tempFile.delete() - + handleSuccess() } private fun handleSuccess() { + Log.w(AttachmentUploadJob.TAG, "Attachment downloaded successfully.") delegate?.handleJobSucceeded(this) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt index 847d4a3b88..259c894136 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt @@ -15,7 +15,6 @@ import org.session.libsignal.service.internal.push.PushAttachmentData import org.session.libsignal.service.internal.push.http.AttachmentCipherOutputStreamFactory import org.session.libsignal.service.internal.util.Util import org.session.libsignal.service.loki.utilities.PlaintextOutputStreamFactory -import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.logging.Log class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job { @@ -45,41 +44,40 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess } override fun execute() { - ThreadUtils.queue { - try { - val attachment = MessagingConfiguration.shared.messageDataProvider.getScaledSignalAttachmentStream(attachmentID) - ?: return@queue handleFailure(Error.NoAttachment) + try { + val attachment = MessagingConfiguration.shared.messageDataProvider.getScaledSignalAttachmentStream(attachmentID) + ?: return handleFailure(Error.NoAttachment) - var server = FileServerAPI.shared.server - var shouldEncrypt = true - val usePadding = false - val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID) - openGroup?.let { - server = it.server - shouldEncrypt = false - } + var server = FileServerAPI.shared.server + var shouldEncrypt = true + val usePadding = false + val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID) + openGroup?.let { + server = it.server + shouldEncrypt = false + } - val attachmentKey = Util.getSecretBytes(64) - val paddedLength = if (usePadding) PaddingInputStream.getPaddedSize(attachment.length) else attachment.length - val dataStream = if (usePadding) PaddingInputStream(attachment.inputStream, attachment.length) else attachment.inputStream - val ciphertextLength = if (shouldEncrypt) AttachmentCipherOutputStream.getCiphertextLength(paddedLength) else attachment.length + val attachmentKey = Util.getSecretBytes(64) + val paddedLength = if (usePadding) PaddingInputStream.getPaddedSize(attachment.length) else attachment.length + val dataStream = if (usePadding) PaddingInputStream(attachment.inputStream, attachment.length) else attachment.inputStream + val ciphertextLength = if (shouldEncrypt) AttachmentCipherOutputStream.getCiphertextLength(paddedLength) else attachment.length - val outputStreamFactory = if (shouldEncrypt) AttachmentCipherOutputStreamFactory(attachmentKey) else PlaintextOutputStreamFactory() - val attachmentData = PushAttachmentData(attachment.contentType, dataStream, ciphertextLength, outputStreamFactory, attachment.listener) + val outputStreamFactory = if (shouldEncrypt) AttachmentCipherOutputStreamFactory(attachmentKey) else PlaintextOutputStreamFactory() + val attachmentData = PushAttachmentData(attachment.contentType, dataStream, ciphertextLength, outputStreamFactory, attachment.listener) - val uploadResult = FileServerAPI.shared.uploadAttachment(server, attachmentData) - handleSuccess(attachment, attachmentKey, uploadResult) + val uploadResult = FileServerAPI.shared.uploadAttachment(server, attachmentData) + handleSuccess(attachment, attachmentKey, uploadResult) - } catch (e: java.lang.Exception) { - if (e is Error && e == Error.NoAttachment) { - this.handlePermanentFailure(e) - } else if (e is DotNetAPI.Error && !e.isRetryable) { - this.handlePermanentFailure(e) - } else { - this.handleFailure(e) - } + } catch (e: java.lang.Exception) { + if (e is Error && e == Error.NoAttachment) { + this.handlePermanentFailure(e) + } else if (e is DotNetAPI.Error && !e.isRetryable) { + this.handlePermanentFailure(e) + } else { + this.handleFailure(e) } } + } private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index dc276d7eb3..a15ae3fb45 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -1,32 +1,51 @@ package org.session.libsession.messaging.jobs +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED +import org.session.libsession.messaging.MessagingConfiguration +import org.session.libsignal.utilities.logging.Log +import java.util.* +import java.util.concurrent.Executors +import kotlin.concurrent.schedule import kotlin.math.min import kotlin.math.pow -import java.util.Timer - -import org.session.libsession.messaging.MessagingConfiguration - -import org.session.libsignal.utilities.logging.Log -import kotlin.concurrent.schedule import kotlin.math.roundToLong class JobQueue : JobDelegate { private var hasResumedPendingJobs = false // Just for debugging + private val dispatcher = Executors.newCachedThreadPool().asCoroutineDispatcher() + private val scope = GlobalScope + SupervisorJob() + private val queue = Channel(UNLIMITED) + + init { + // process jobs + scope.launch { + while (isActive) { + queue.receive().let { job -> + launch(dispatcher) { + job.delegate = this@JobQueue + job.execute() + } + } + } + } + } + companion object { val shared: JobQueue by lazy { JobQueue() } } fun add(job: Job) { addWithoutExecuting(job) - job.execute() + queue.offer(job) // offer always called on unlimited capacity } fun addWithoutExecuting(job: Job) { job.id = System.currentTimeMillis().toString() MessagingConfiguration.shared.storage.persistJob(job) - job.delegate = this } fun resumePendingJobs() { @@ -40,8 +59,7 @@ class JobQueue : JobDelegate { val allPendingJobs = MessagingConfiguration.shared.storage.getAllPendingJobs(type) allPendingJobs.sortedBy { it.id }.forEach { job -> Log.i("Jobs", "Resuming pending job of type: ${job::class.simpleName}.") - job.delegate = this - job.execute() + queue.offer(job) // offer always called on unlimited capacity } } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt index 4ce46b4577..9dac6de010 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt @@ -38,13 +38,13 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val this.handleSuccess() deferred.resolve(Unit) } catch (e: Exception) { - Log.d(TAG, "Couldn't receive message due to error: $e.") + Log.e(TAG, "Couldn't receive message due to error", e) val error = e as? MessageReceiver.Error if (error != null && !error.isRetryable) { - Log.d("Loki", "Message receive job permanently failed due to error: $error.") + Log.e("Loki", "Message receive job permanently failed due to error", e) this.handlePermanentFailure(error) } else { - Log.d("Loki", "Couldn't receive message due to error: $e.") + Log.e("Loki", "Couldn't receive message due to error", e) this.handleFailure(e) } deferred.resolve(Unit) // The promise is just used to keep track of when we're done diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Attachment.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Attachment.kt index edb9d7767b..1e4e669284 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Attachment.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Attachment.kt @@ -3,12 +3,12 @@ package org.session.libsession.messaging.messages.visible import android.util.Size import android.webkit.MimeTypeMap import com.google.protobuf.ByteString -import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer -import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.session.libsignal.utilities.Base64 import java.io.File +import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment class Attachment { @@ -101,7 +101,7 @@ class Attachment { fun toSignalAttachment(): SignalAttachment? { if (!isValid()) return null return DatabaseAttachment(null, 0, false, false, contentType, 0, - sizeInBytes?.toLong() ?: 0, fileName, null, key.toString(), null, digest, null, kind == Kind.VOICE_MESSAGE, + sizeInBytes?.toLong() ?: 0, if (fileName.isNullOrEmpty()) null else fileName, null, Base64.encodeBytes(key), null, digest, null, kind == Kind.VOICE_MESSAGE, size?.width ?: 0, size?.height ?: 0, false, caption, url) } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt index 96452cd58a..8e21f42da4 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt @@ -12,7 +12,7 @@ class VisibleMessage : Message() { var syncTarget: String? = null var text: String? = null - var attachmentIDs:List = mutableListOf() + val attachmentIDs: MutableList = mutableListOf() var quote: Quote? = null var linkPreview: LinkPreview? = null var contact: Contact? = null @@ -51,7 +51,7 @@ class VisibleMessage : Message() { val databaseAttachment = it as DatabaseAttachment databaseAttachment.attachmentId.rowId } - this.attachmentIDs = attachmentIDs as ArrayList + this.attachmentIDs.addAll(attachmentIDs) } fun isMediaMessage(): Boolean { diff --git a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessage.kt index cc8cff5b13..bdccc5b2e5 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessage.kt @@ -2,9 +2,9 @@ package org.session.libsession.messaging.opengroups import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.messages.visible.VisibleMessage -import org.session.libsignal.utilities.logging.Log -import org.session.libsignal.utilities.Hex import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded +import org.session.libsignal.utilities.Hex +import org.session.libsignal.utilities.logging.Log import org.whispersystems.curve25519.Curve25519 data class OpenGroupMessage( @@ -26,7 +26,7 @@ data class OpenGroupMessage( fun from(message: VisibleMessage, server: String): OpenGroupMessage? { val storage = MessagingConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey() ?: return null - var attachmentIDs = message.attachmentIDs + val attachmentIDs = message.attachmentIDs // Validation if (!message.isValid()) { return null } // Should be valid at this point // Quote diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index af0f169ba5..c367b5182e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -122,7 +122,7 @@ object MessageReceiver { message.openGroupServerMessageID = openGroupServerID // Validate var isValid = message.isValid() - if (message is VisibleMessage && !isValid && proto.dataMessage.attachmentsCount == 0) { isValid = true } + if (message is VisibleMessage && !isValid && proto.dataMessage.attachmentsCount != 0) { isValid = true } if (!isValid) { throw Error.InvalidMessage } // Return return Pair(message, proto) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index bc61dde34d..10c0420c12 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -133,7 +133,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS } } val attachmentIDs = storage.persistAttachments(message.id ?: 0, attachments) - message.attachmentIDs = attachmentIDs.toMutableList() + message.attachmentIDs.addAll(attachmentIDs.toMutableList()) // Update profile if needed val newProfile = message.profile if (newProfile != null) { From 817c40b30ccb2e2fccae92e774ebc473f371050a Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 15 Mar 2021 13:35:05 +1100 Subject: [PATCH 04/51] refactor: inserting attachments with the messages so that they are linked properly to a mmsID --- .../securesms/database/Storage.kt | 41 ++++++++++--------- .../securesms/mms/IncomingMediaMessage.java | 8 ++-- .../libsession/messaging/StorageProtocol.kt | 6 +-- .../messaging/messages/visible/Attachment.kt | 11 ++++- .../MessageReceiverHandler.kt | 30 +++++++------- 5 files changed, 53 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 853fc3debe..3b70783d9a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -15,7 +15,7 @@ import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.opengroups.OpenGroup import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId -import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment +import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.threads.Address @@ -27,13 +27,11 @@ import org.session.libsession.utilities.preferences.ProfileKeyUtil import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.util.KeyHelper import org.session.libsignal.libsignal.util.guava.Optional -import org.session.libsignal.service.api.messages.SignalServiceAttachment import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.loki.api.opengroups.PublicChat import org.session.libsignal.utilities.logging.Log -import org.thoughtcrime.securesms.attachments.toSignalPointer import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase @@ -94,17 +92,24 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return database.insertAttachments(messageId, databaseAttachments) } - override fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?): Long? { + override fun getAttachmentsForMessage(messageId: Long): List { + val database = DatabaseFactory.getAttachmentDatabase(context) + return database.getAttachmentsForMessage(messageId) + } + + override fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? { var messageID: Long? = null val senderAddress = Address.fromSerialized(message.sender!!) val senderRecipient = Recipient.from(context, senderAddress, false) - var group: Optional = Optional.absent() - if (openGroupID != null) { - group = Optional.of(SignalServiceGroup(openGroupID.toByteArray(), SignalServiceGroup.GroupType.PUBLIC_CHAT)) - } else if (groupPublicKey != null) { - group = Optional.of(SignalServiceGroup(groupPublicKey.toByteArray(), SignalServiceGroup.GroupType.SIGNAL)) + val group: Optional = when { + openGroupID != null -> Optional.of(SignalServiceGroup(openGroupID.toByteArray(), SignalServiceGroup.GroupType.PUBLIC_CHAT)) + groupPublicKey != null -> Optional.of(SignalServiceGroup(groupPublicKey.toByteArray(), SignalServiceGroup.GroupType.SIGNAL)) + else -> Optional.absent() } - if (message.isMediaMessage()) { + val pointerAttachments = attachments.mapNotNull { + it.toSignalAttachment() + } + if (message.isMediaMessage() || attachments.isNotEmpty()) { 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 mmsDatabase = DatabaseFactory.getMmsDatabase(context) @@ -119,20 +124,16 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return null } } - val attachments = message.attachmentIDs.mapNotNull { - DatabaseFactory.getAttachmentProvider(context).getSignalAttachmentPointer(it) - }.mapNotNull { - PointerAttachment.forPointer(Optional.of(it)).orNull() - } - val mediaMessage = OutgoingMediaMessage.from(message, Recipient.from(context, targetAddress, false), attachments, quote.orNull(), linkPreviews.orNull().firstOrNull()) + + val mediaMessage = OutgoingMediaMessage.from(message, Recipient.from(context, targetAddress, false), pointerAttachments, quote.orNull(), linkPreviews.orNull().firstOrNull()) mmsDatabase.beginTransaction() mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) } else { // It seems like we have replaced SignalServiceAttachment with SessionServiceAttachment - val attachments: Optional> = Optional.of(message.attachmentIDs.mapNotNull { - DatabaseFactory.getAttachmentProvider(context).getAttachmentPointer(it)?.toSignalPointer() - }) - val mediaMessage = IncomingMediaMessage.from(message, senderAddress, senderRecipient.expireMessages * 1000L, group, attachments, quote, linkPreviews) + val signalServiceAttachments = attachments.mapNotNull { + it.toSignalPointer() + } + val mediaMessage = IncomingMediaMessage.from(message, senderAddress, senderRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews) mmsDatabase.beginTransaction() if (group.isPresent) { mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/app/src/main/java/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index 7c1451056f..a709efa339 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -3,10 +3,10 @@ package org.thoughtcrime.securesms.mms; 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.sharecontacts.Contact; -import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; +import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; +import org.session.libsession.messaging.threads.Address; import org.session.libsession.utilities.GroupUtil; import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.service.api.messages.SignalServiceAttachment; @@ -68,12 +68,12 @@ public class IncomingMediaMessage { Address from, long expiresIn, Optional group, - Optional> attachments, + List attachments, Optional quote, Optional> linkPreviews) { return new IncomingMediaMessage(from, message.getReceivedTimestamp(), -1, expiresIn, false, - false, Optional.fromNullable(message.getText()), group, attachments, quote, Optional.absent(), linkPreviews); + false, Optional.fromNullable(message.getText()), group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews); } public int getSubscriptionId() { diff --git a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt index a7bb7e0dc1..c4535f5055 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -6,18 +6,17 @@ import android.net.Uri import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.MessageSendJob -import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.opengroups.OpenGroup import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId +import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.GroupRecord import org.session.libsession.messaging.threads.recipients.Recipient.RecipientSettings import org.session.libsignal.libsignal.ecc.ECKeyPair -import org.session.libsignal.libsignal.ecc.ECPrivateKey import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos @@ -92,6 +91,7 @@ interface StorageProtocol { // fun removeReceivedMessageTimestamps(timestamps: Set) // Returns the IDs of the saved attachments. fun persistAttachments(messageId: Long, attachments: List): List + fun getAttachmentsForMessage(messageId: Long): List fun getMessageIdInDatabase(timestamp: Long, author: String): Long? fun setOpenGroupServerMessageID(messageID: Long, serverID: Long) @@ -150,5 +150,5 @@ interface StorageProtocol { // Message Handling /// Returns the ID of the `TSIncomingMessage` that was constructed. - fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?): Long? + fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Attachment.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Attachment.kt index 1e4e669284..24226c7274 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Attachment.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Attachment.kt @@ -4,6 +4,7 @@ import android.util.Size import android.webkit.MimeTypeMap import com.google.protobuf.ByteString import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment +import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.utilities.Base64 @@ -23,7 +24,7 @@ class Attachment { var url: String? = null companion object { - fun fromProto(proto: SignalServiceProtos.AttachmentPointer): Attachment? { + fun fromProto(proto: SignalServiceProtos.AttachmentPointer): Attachment { val result = Attachment() result.fileName = proto.fileName fun inferContentType(): String { @@ -104,4 +105,12 @@ class Attachment { sizeInBytes?.toLong() ?: 0, if (fileName.isNullOrEmpty()) null else fileName, null, Base64.encodeBytes(key), null, digest, null, kind == Kind.VOICE_MESSAGE, size?.width ?: 0, size?.height ?: 0, false, caption, url) } + + fun toSignalPointer(): SignalServiceAttachmentPointer? { + if (!isValid()) return null + return SignalServiceAttachmentPointer(0, contentType, key, Optional.fromNullable(sizeInBytes), null, + size?.width ?: 0, size?.height ?: 0, Optional.fromNullable(digest), Optional.fromNullable(fileName), + kind == Kind.VOICE_MESSAGE, Optional.fromNullable(caption), url) + } + } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index 10c0420c12..e3718534f8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -123,17 +123,6 @@ private fun MessageReceiver.handleConfigurationMessage(message: ConfigurationMes fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalServiceProtos.Content, openGroupID: String?) { val storage = MessagingConfiguration.shared.storage val context = MessagingConfiguration.shared.context - // Parse & persist attachments - val attachments = proto.dataMessage.attachmentsList.mapNotNull { proto -> - val attachment = Attachment.fromProto(proto) - if (attachment == null || !attachment.isValid()) { - return@mapNotNull null - } else { - return@mapNotNull attachment - } - } - val attachmentIDs = storage.persistAttachments(message.id ?: 0, attachments) - message.attachmentIDs.addAll(attachmentIDs.toMutableList()) // Update profile if needed val newProfile = message.profile if (newProfile != null) { @@ -187,14 +176,25 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS } } } + val attachments = proto.dataMessage.attachmentsList.mapNotNull { proto -> + val attachment = Attachment.fromProto(proto) + if (!attachment.isValid()) { + return@mapNotNull null + } else { + return@mapNotNull attachment + } + } // Parse stickers if needed // Persist the message - val messageID = storage.persist(message, quoteModel, linkPreviews, message.groupPublicKey, openGroupID) ?: throw MessageReceiver.Error.NoThread + val messageID = storage.persist(message, quoteModel, linkPreviews, message.groupPublicKey, openGroupID, attachments) ?: throw MessageReceiver.Error.NoThread message.threadID = threadID + // Parse & persist attachments // Start attachment downloads if needed - attachmentIDs.forEach { attachmentID -> - val downloadJob = AttachmentDownloadJob(attachmentID, messageID) - JobQueue.shared.add(downloadJob) + storage.getAttachmentsForMessage(messageID).forEach { attachment -> + attachment.attachmentId?.let { id -> + val downloadJob = AttachmentDownloadJob(id.rowId, messageID) + JobQueue.shared.add(downloadJob) + } } // Cancel any typing indicators if needed cancelTypingIndicatorsIfNeeded(message.sender!!) From 6508873e57b1e5dcfce716e455fe66df2bacae8b Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 15 Mar 2021 15:03:23 +1100 Subject: [PATCH 05/51] fix: fix the sent / receive timestamps for linking quotes --- .../thoughtcrime/securesms/database/Storage.kt | 18 ++++++------------ .../messages/signal/IncomingTextMessage.java | 3 ++- .../sending_receiving/MessageSender.kt | 11 +++++------ 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 3b70783d9a..e23dd2a6e3 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 org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageSendJob +import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage import org.session.libsession.messaging.messages.signal.IncomingGroupMessage import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage @@ -135,11 +136,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } val mediaMessage = IncomingMediaMessage.from(message, senderAddress, senderRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews) mmsDatabase.beginTransaction() - if (group.isPresent) { - mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) - } else { - mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1) - } + mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp ?: 0) } if (insertResult.isPresent) { mmsDatabase.setTransactionSuccessful() @@ -163,14 +160,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, smsDatabase.insertMessageOutbox(message.threadID ?: -1, textMessage, message.sentTimestamp!!) } else { val textMessage = IncomingTextMessage.from(message, senderAddress, group, senderRecipient.expireMessages * 1000L) - if (group.isPresent) { - smsDatabase.insertMessageInbox(textMessage, message.sentTimestamp!!) - } else { - smsDatabase.insertMessageInbox(textMessage) - } + val encrypted = IncomingEncryptedMessage(textMessage, textMessage.messageBody) + smsDatabase.insertMessageInbox(encrypted, message.sentTimestamp ?: 0) } - if (insertResult.isPresent) { - messageID = insertResult.get().messageId + insertResult.orNull()?.let { result -> + messageID = result.messageId } } return messageID diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java index 31be71cf3d..35b9d40a2c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java @@ -2,6 +2,7 @@ package org.session.libsession.messaging.messages.signal; import android.os.Parcel; import android.os.Parcelable; + import androidx.annotation.Nullable; import org.session.libsession.messaging.messages.visible.VisibleMessage; @@ -100,7 +101,7 @@ public class IncomingTextMessage implements Parcelable { Optional group, long expiresInMillis) { - return new IncomingTextMessage(sender, 1, message.getReceivedTimestamp(), message.getText(), group, expiresInMillis, false); + return new IncomingTextMessage(sender, 1, message.getSentTimestamp(), message.getText(), group, expiresInMillis, false); } public int getSubscriptionId() { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 5fae947a38..675ff03553 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -12,9 +12,6 @@ import org.session.libsession.messaging.messages.control.ClosedGroupControlMessa import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.visible.* -import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment -import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview as SignalLinkPreview -import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote import org.session.libsession.messaging.opengroups.OpenGroupAPI import org.session.libsession.messaging.opengroups.OpenGroupMessage import org.session.libsession.messaging.threads.Address @@ -29,6 +26,9 @@ import org.session.libsignal.service.loki.api.crypto.ProofOfWork import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.logging.Log +import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment +import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview as SignalLinkPreview +import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote object MessageSender { @@ -151,10 +151,9 @@ object MessageSender { } val recipient = message.recipient!! val base64EncodedData = Base64.encodeBytes(wrappedMessage) - val timestamp = System.currentTimeMillis() - val nonce = ProofOfWork.calculate(base64EncodedData, recipient, timestamp, message.ttl.toInt()) ?: throw Error.ProofOfWorkCalculationFailed + val nonce = ProofOfWork.calculate(base64EncodedData, recipient, message.sentTimestamp!!, message.ttl.toInt()) ?: throw Error.ProofOfWorkCalculationFailed // Send the result - val snodeMessage = SnodeMessage(recipient, base64EncodedData, message.ttl, timestamp, nonce) + val snodeMessage = SnodeMessage(recipient, base64EncodedData, message.ttl, message.sentTimestamp!!, nonce) if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { SnodeConfiguration.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!) } From b81d9b5e3a541e83d0717bad18b5b5ed26e6f758 Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 15 Mar 2021 16:44:20 +1100 Subject: [PATCH 06/51] fix: matching up the remove 05 prefixing to be consistent through the new pipeline call sites --- .../messaging/messages/control/ClosedGroupControlMessage.kt | 3 ++- .../messaging/sending_receiving/MessageSenderClosedGroup.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt index 14ecbf4f0f..7654f6ce9a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt @@ -6,6 +6,7 @@ import org.session.libsignal.libsignal.ecc.DjbECPublicKey import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage +import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.logging.Log @@ -141,7 +142,7 @@ class ClosedGroupControlMessage() : ControlMessage() { closedGroupControlMessage.publicKey = kind.publicKey closedGroupControlMessage.name = kind.name val encryptionKeyPairAsProto = SignalServiceProtos.KeyPair.newBuilder() - encryptionKeyPairAsProto.publicKey = ByteString.copyFrom(kind.encryptionKeyPair.publicKey.serialize()) + encryptionKeyPairAsProto.publicKey = ByteString.copyFrom(kind.encryptionKeyPair.publicKey.serialize().removing05PrefixIfNeeded()) encryptionKeyPairAsProto.privateKey = ByteString.copyFrom(kind.encryptionKeyPair.privateKey.serialize()) try { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt index 8f49045ed8..78d5b25e98 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt @@ -286,7 +286,7 @@ fun MessageSender.sendLatestEncryptionKeyPair(publicKey: String, groupPublicKey: ?: storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: return // Send it val proto = SignalServiceProtos.KeyPair.newBuilder() - proto.publicKey = ByteString.copyFrom(encryptionKeyPair.publicKey.serialize()) + proto.publicKey = ByteString.copyFrom(encryptionKeyPair.publicKey.serialize().removing05PrefixIfNeeded()) proto.privateKey = ByteString.copyFrom(encryptionKeyPair.privateKey.serialize()) val plaintext = proto.build().toByteArray() val ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, publicKey) From fe3f3d5f88d06610006c1b966f2d1cc93cb84c14 Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 15 Mar 2021 16:44:44 +1100 Subject: [PATCH 07/51] refactor: remove redundant Companion qualifier --- .../securesms/loki/database/LokiAPIDatabase.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt index 077715f7e6..385190cfdb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt @@ -2,21 +2,21 @@ package org.thoughtcrime.securesms.loki.database import android.content.ContentValues import android.content.Context +import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.libsignal.ecc.DjbECPrivateKey import org.session.libsignal.libsignal.ecc.DjbECPublicKey import org.session.libsignal.libsignal.ecc.ECKeyPair +import org.session.libsignal.service.loki.api.Snode +import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol +import org.session.libsignal.service.loki.utilities.PublicKeyValidation +import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded +import org.session.libsignal.service.loki.utilities.toHexString +import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.logging.Log +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.utilities.* -import org.session.libsignal.service.loki.api.Snode -import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol -import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded -import org.session.libsignal.service.loki.utilities.toHexString -import org.thoughtcrime.securesms.crypto.IdentityKeyUtil -import org.session.libsignal.utilities.Hex -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.service.loki.utilities.PublicKeyValidation import java.util.* class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiAPIDatabaseProtocol { @@ -416,7 +416,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( val index = "$groupPublicKey-$timestamp" val encryptionKeyPairPublicKey = encryptionKeyPair.publicKey.serialize().toHexString().removing05PrefixIfNeeded() val encryptionKeyPairPrivateKey = encryptionKeyPair.privateKey.serialize().toHexString() - val row = wrap(mapOf( Companion.closedGroupsEncryptionKeyPairIndex to index, Companion.encryptionKeyPairPublicKey to encryptionKeyPairPublicKey, + val row = wrap(mapOf(closedGroupsEncryptionKeyPairIndex to index, Companion.encryptionKeyPairPublicKey to encryptionKeyPairPublicKey, Companion.encryptionKeyPairPrivateKey to encryptionKeyPairPrivateKey )) database.insertOrUpdate(closedGroupEncryptionKeyPairsTable, row, "${Companion.closedGroupsEncryptionKeyPairIndex} = ?", wrap(index)) } From dd8a0cdc2e5d40fb00307d19a6673b6b9dc17bdc Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 16 Mar 2021 09:44:55 +1100 Subject: [PATCH 08/51] fix: use queue in the retry timer --- .../main/java/org/session/libsession/messaging/jobs/JobQueue.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index a15ae3fb45..874ce6e0cc 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -80,7 +80,7 @@ class JobQueue : JobDelegate { Log.i("Jobs", "${job::class.simpleName} failed; scheduling retry (failure count is ${job.failureCount}).") Timer().schedule(delay = retryInterval) { Log.i("Jobs", "Retrying ${job::class.simpleName}.") - job.execute() + queue.offer(job) } } } From 95646ed04eb9da9a9af6cbf2f2c9a622150d5f49 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Tue, 16 Mar 2021 16:31:52 +1100 Subject: [PATCH 09/51] make error description show in the message detail activity --- .../thoughtcrime/securesms/database/Storage.kt | 6 ++++++ .../messaging/jobs/AttachmentDownloadJob.kt | 2 +- .../messaging/jobs/AttachmentUploadJob.kt | 2 +- .../sending_receiving/MessageReceiver.kt | 2 +- .../messaging/sending_receiving/MessageSender.kt | 16 +++++++--------- .../libsession/messaging/utilities/DotNetAPI.kt | 2 +- .../messaging/utilities/MessageWrapper.kt | 2 +- .../org/session/libsession/snode/SnodeAPI.kt | 2 +- .../service/loki/crypto/MnemonicCodec.kt | 2 +- 9 files changed, 20 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 6a0d42777d..3cb1887381 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -44,6 +44,7 @@ import org.session.libsession.messaging.messages.signal.IncomingGroupMessage import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.utilities.preferences.ProfileKeyUtil +import org.session.libsignal.service.loki.utilities.prettifiedDescription class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { override fun getUserPublicKey(): String? { @@ -368,6 +369,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val smsDatabase = DatabaseFactory.getSmsDatabase(context) smsDatabase.markAsSentFailed(messageRecord.getId()) } + if (error.localizedMessage != null) { + DatabaseFactory.getLokiMessageDatabase(context).setErrorMessage(messageRecord.getId(), error.localizedMessage!!) + } else { + DatabaseFactory.getLokiMessageDatabase(context).setErrorMessage(messageRecord.getId(), error.javaClass.simpleName) + } } override fun getGroup(groupID: String): GroupRecord? { diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index 37eafe8b1b..f46a124ca8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -17,7 +17,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) private val MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024 // Error - internal sealed class Error(val description: String) : Exception() { + internal sealed class Error(val description: String) : Exception(description) { object NoAttachment : Error("No such attachment.") } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt index 847d4a3b88..94b7a9d26a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt @@ -25,7 +25,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess override var failureCount: Int = 0 // Error - internal sealed class Error(val description: String) : Exception() { + internal sealed class Error(val description: String) : Exception(description) { object NoAttachment : Error("No such attachment.") } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index 5b661327cb..8412ddcc61 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -11,7 +11,7 @@ object MessageReceiver { private val lastEncryptionKeyPairRequest = mutableMapOf() - internal sealed class Error(val description: String) : Exception() { + internal sealed class Error(val description: String) : Exception(description) { object DuplicateMessage: Error("Duplicate message.") object InvalidMessage: Error("Invalid message.") object UnknownMessage: Error("Unknown message type.") diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 39a0237000..ebab94b9aa 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -34,7 +34,7 @@ import org.session.libsignal.utilities.logging.Log object MessageSender { // Error - sealed class Error(val description: String) : Exception() { + sealed class Error(val description: String) : Exception(description) { object InvalidMessage : Error("Invalid message.") object ProtoConversionFailed : Error("Couldn't convert message to proto.") object ProofOfWorkCalculationFailed : Error("Proof of work calculation failed.") @@ -202,6 +202,11 @@ object MessageSender { val preconditionFailure = Exception("Destination should not be contacts or closed groups!") message.sentTimestamp ?: run { message.sentTimestamp = System.currentTimeMillis() } message.sender = storage.getUserPublicKey() + // Set the failure handler (need it here already for precondition failure handling) + fun handleFailure(error: Exception) { + handleFailedMessageSend(message, error) + deferred.reject(error) + } try { val server: String val channel: Long @@ -214,19 +219,12 @@ object MessageSender { channel = destination.channel } } - // Set the failure handler (need it here already for precondition failure handling) - fun handleFailure(error: Exception) { - handleFailedMessageSend(message, error) - deferred.reject(error) - } // Validate the message if (message !is VisibleMessage || !message.isValid()) { - handleFailure(Error.InvalidMessage) throw Error.InvalidMessage } // Convert the message to an open group message val openGroupMessage = OpenGroupMessage.from(message, server) ?: kotlin.run { - handleFailure(Error.InvalidMessage) throw Error.InvalidMessage } // Send the result @@ -238,7 +236,7 @@ object MessageSender { handleFailure(it) } } catch (exception: Exception) { - deferred.reject(exception) + handleFailure(exception) } return deferred.promise } diff --git a/libsession/src/main/java/org/session/libsession/messaging/utilities/DotNetAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/utilities/DotNetAPI.kt index 49853b33e2..0856e43862 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/DotNetAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/DotNetAPI.kt @@ -42,7 +42,7 @@ open class DotNetAPI { internal enum class HTTPVerb { GET, PUT, POST, DELETE, PATCH } // Error - internal sealed class Error(val description: String) : Exception() { + internal sealed class Error(val description: String) : Exception(description) { object Generic : Error("An error occurred.") object InvalidURL : Error("Invalid URL.") object ParsingFailed : Error("Invalid file server response.") diff --git a/libsession/src/main/java/org/session/libsession/messaging/utilities/MessageWrapper.kt b/libsession/src/main/java/org/session/libsession/messaging/utilities/MessageWrapper.kt index 9305852258..b11cbe1774 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/MessageWrapper.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/MessageWrapper.kt @@ -10,7 +10,7 @@ import java.security.SecureRandom object MessageWrapper { // region Types - sealed class Error(val description: String) : Exception() { + sealed class Error(val description: String) : Exception(description) { object FailedToWrapData : Error("Failed to wrap data.") object FailedToWrapMessageInEnvelope : Error("Failed to wrap message in envelope.") object FailedToWrapEnvelopeInWebSocketMessage : Error("Failed to wrap envelope in web socket message.") diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 1b17a02211..98b711af60 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -45,7 +45,7 @@ object SnodeAPI { internal var powDifficulty = 1 // Error - internal sealed class Error(val description: String) : Exception() { + internal sealed class Error(val description: String) : Exception(description) { object Generic : Error("An error occurred.") object ClockOutOfSync : Error("The user's clock is out of sync with the service node network.") object RandomSnodePoolUpdatingFailed : Error("Failed to update random service node pool.") diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/crypto/MnemonicCodec.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/crypto/MnemonicCodec.kt index 192218d399..b3c15dd132 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/crypto/MnemonicCodec.kt +++ b/libsignal/src/main/java/org/session/libsignal/service/loki/crypto/MnemonicCodec.kt @@ -50,7 +50,7 @@ class MnemonicCodec(private val loadFileContents: (String) -> String) { } } - sealed class DecodingError(val description: String) : Exception() { + sealed class DecodingError(val description: String) : Exception(description) { object Generic : DecodingError("Something went wrong. Please check your mnemonic and try again.") object InputTooShort : DecodingError("Looks like you didn't enter enough words. Please check your mnemonic and try again.") object MissingLastWord : DecodingError("You seem to be missing the last word of your mnemonic. Please check what you entered and try again.") From 39aef68b0428813b4775318099031b89480d10f9 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Wed, 17 Mar 2021 10:48:20 +1100 Subject: [PATCH 10/51] refactor precondition failure error --- .../messaging/sending_receiving/MessageSender.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index ff07ae0ddc..9ccd58fc72 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -50,6 +50,9 @@ object MessageSender { object NoPrivateKey : Error("Couldn't find a private key associated with the given group public key.") object InvalidClosedGroupUpdate : Error("Invalid group update.") + // Precondition + class PreconditionFailure(val reason: String): Error(reason) + internal val isRetryable: Boolean = when (this) { is InvalidMessage -> false is ProtoConversionFailed -> false @@ -73,7 +76,6 @@ object MessageSender { val promise = deferred.promise val storage = MessagingConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey() - val preconditionFailure = Exception("Destination should not be open groups!") // Set the timestamp, sender and recipient message.sentTimestamp ?: run { message.sentTimestamp = System.currentTimeMillis() } /* Visible messages will already have their sent timestamp set */ message.sender = userPublicKey @@ -90,7 +92,7 @@ object MessageSender { when (destination) { is Destination.Contact -> message.recipient = destination.publicKey is Destination.ClosedGroup -> message.recipient = destination.groupPublicKey - is Destination.OpenGroup -> throw preconditionFailure + is Destination.OpenGroup -> throw Error.PreconditionFailure("Destination should not be open groups!") } // Validate the message if (!message.isValid()) { throw Error.InvalidMessage } @@ -128,7 +130,7 @@ object MessageSender { val encryptionKeyPair = MessagingConfiguration.shared.storage.getLatestClosedGroupEncryptionKeyPair(destination.groupPublicKey)!! ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, encryptionKeyPair.hexEncodedPublicKey) } - is Destination.OpenGroup -> throw preconditionFailure + is Destination.OpenGroup -> throw Error.PreconditionFailure("Destination should not be open groups!") } // Wrap the result val kind: SignalServiceProtos.Envelope.Type @@ -142,7 +144,7 @@ object MessageSender { kind = SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT senderPublicKey = destination.groupPublicKey } - is Destination.OpenGroup -> throw preconditionFailure + is Destination.OpenGroup -> throw Error.PreconditionFailure("Destination should not be open groups!") } val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext) // Calculate proof of work @@ -200,7 +202,6 @@ object MessageSender { private fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise { val deferred = deferred() val storage = MessagingConfiguration.shared.storage - val preconditionFailure = Exception("Destination should not be contacts or closed groups!") message.sentTimestamp ?: run { message.sentTimestamp = System.currentTimeMillis() } message.sender = storage.getUserPublicKey() // Set the failure handler (need it here already for precondition failure handling) @@ -212,8 +213,8 @@ object MessageSender { val server: String val channel: Long when (destination) { - is Destination.Contact -> throw preconditionFailure - is Destination.ClosedGroup -> throw preconditionFailure + is Destination.Contact -> throw Error.PreconditionFailure("Destination should not be contacts!") + is Destination.ClosedGroup -> throw Error.PreconditionFailure("Destination should not be closed groups!") is Destination.OpenGroup -> { message.recipient = "${destination.server}.${destination.channel}" server = destination.server From 436598a80b91db390406bb3be7e06f33473c56ac Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Wed, 17 Mar 2021 11:30:03 +1100 Subject: [PATCH 11/51] resume pending jobs on app starting --- .../org/thoughtcrime/securesms/loki/activities/HomeActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index bcbdc18ef8..281d42b552 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -30,6 +30,7 @@ import org.session.libsession.messaging.sending_receiving.MessageSender import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode +import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.utilities.* import org.session.libsignal.service.loki.utilities.mentions.MentionsManager import org.session.libsignal.service.loki.utilities.toHexString @@ -139,6 +140,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), if (userPublicKey != null) { MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB) application.publicChatManager.startPollersIfNeeded() + JobQueue.shared.resumePendingJobs() } IP2Country.configureIfNeeded(this) application.registerForFCMIfNeeded(false) From 1e9e580a2ffadd92e209a097e7ab0171b6dd4df1 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Wed, 17 Mar 2021 12:06:44 +1100 Subject: [PATCH 12/51] minor refactor --- .../securesms/loki/database/SessionJobDatabase.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt index 9ae1659ca0..1d4b81486c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt @@ -85,10 +85,7 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa } } -class SessionJobHelper() { - - companion object { - val dataSerializer: Data.Serializer = JsonDataSerializer() - val sessionJobInstantiator: SessionJobInstantiator = SessionJobInstantiator(SessionJobManagerFactories.getSessionJobFactories()) - } +object SessionJobHelper { + val dataSerializer: Data.Serializer = JsonDataSerializer() + val sessionJobInstantiator: SessionJobInstantiator = SessionJobInstantiator(SessionJobManagerFactories.getSessionJobFactories()) } \ No newline at end of file From 80382cc454685d71e38a446acbd75d750e74fb17 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Wed, 17 Mar 2021 16:09:01 +1100 Subject: [PATCH 13/51] minor refactor --- .../securesms/loki/database/SessionJobDatabase.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt index 1d4b81486c..dda3b7d7eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt @@ -12,11 +12,11 @@ import org.thoughtcrime.securesms.loki.utilities.* class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) { companion object { - private val sessionJobTable = "session_job_database" - val jobID = "job_id" - val jobType = "job_type" - val failureCount = "failure_count" - val serializedData = "serialized_data" + private const val sessionJobTable = "session_job_database" + const val jobID = "job_id" + const val jobType = "job_type" + const val failureCount = "failure_count" + const val serializedData = "serialized_data" @JvmStatic val createSessionJobTableCommand = "CREATE TABLE $sessionJobTable ($jobID INTEGER PRIMARY KEY, $jobType STRING, $failureCount INTEGER DEFAULT 0, $serializedData TEXT);" } From 333533785bfb2a31ca66a64ccf2a6115f7c15246 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Wed, 17 Mar 2021 16:22:43 +1100 Subject: [PATCH 14/51] fix crash on attachment send failure --- .../messaging/sending_receiving/MessageSender.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 9ccd58fc72..7344b3f17c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -246,7 +246,8 @@ object MessageSender { // Result Handling fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false) { val storage = MessagingConfiguration.shared.storage - val messageId = storage.getMessageIdInDatabase(message.sentTimestamp!!, message.sender!!) ?: return + val userPublicKey = storage.getUserPublicKey()!! + val messageId = storage.getMessageIdInDatabase(message.sentTimestamp!!, message.sender?:userPublicKey) ?: return // Ignore future self-sends storage.addReceivedMessageTimestamp(message.sentTimestamp!!) // Track the open group server message ID @@ -254,17 +255,16 @@ object MessageSender { storage.setOpenGroupServerMessageID(messageId, message.openGroupServerMessageID!!) } // Mark the message as sent - storage.markAsSent(message.sentTimestamp!!, message.sender!!) - storage.markUnidentified(message.sentTimestamp!!, message.sender!!) + storage.markAsSent(message.sentTimestamp!!, message.sender?:userPublicKey) + storage.markUnidentified(message.sentTimestamp!!, message.sender?:userPublicKey) // Start the disappearing messages timer if needed if (message is VisibleMessage && !isSyncMessage) { - SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message.sentTimestamp!!, message.sender!!) + SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message.sentTimestamp!!, message.sender?:userPublicKey) } // Sync the message if: // • it's a visible message // • the destination was a contact // • we didn't sync it already - val userPublicKey = storage.getUserPublicKey()!! if (destination is Destination.Contact && !isSyncMessage) { if (message is VisibleMessage) { message.syncTarget = destination.publicKey } if (message is ExpirationTimerUpdate) { message.syncTarget = destination.publicKey } @@ -274,7 +274,8 @@ object MessageSender { fun handleFailedMessageSend(message: Message, error: Exception) { val storage = MessagingConfiguration.shared.storage - storage.setErrorMessage(message.sentTimestamp!!, message.sender!!, error) + val userPublicKey = storage.getUserPublicKey()!! + storage.setErrorMessage(message.sentTimestamp!!, message.sender?:userPublicKey, error) } // Convenience From 203c0dd44c268f51f5a94ea9a9d5684dd2171759 Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 18 Mar 2021 09:52:20 +1100 Subject: [PATCH 15/51] refactor: fix imports / compile issues from merging dev --- .../securesms/ApplicationContext.java | 18 +----------------- .../securesms/loki/database/LokiAPIDatabase.kt | 3 +-- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 0d230be39b..623c140474 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -36,9 +36,8 @@ import org.session.libsession.messaging.sending_receiving.notifications.MessageN import org.session.libsession.messaging.sending_receiving.pollers.Poller; import org.session.libsession.messaging.threads.Address; import org.session.libsession.snode.SnodeConfiguration; +import org.session.libsession.utilities.IdentityKeyUtil; import org.session.libsession.utilities.SSKEnvironment; -import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; -import org.session.libsession.messaging.threads.Address; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper; @@ -53,23 +52,8 @@ import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI; import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol; import org.session.libsignal.service.loki.utilities.mentions.MentionsManager; import org.session.libsignal.utilities.logging.Log; -import org.session.libsession.utilities.preferences.ProfileKeyUtil; -import org.session.libsignal.service.api.messages.SignalServiceEnvelope; -import org.session.libsignal.service.api.util.StreamDetails; -import org.session.libsignal.service.internal.push.SignalServiceProtos; -import org.session.libsignal.service.loki.api.Poller; -import org.session.libsignal.service.loki.api.PushNotificationAPI; -import org.session.libsignal.service.loki.api.SnodeAPI; -import org.session.libsignal.service.loki.api.SwarmAPI; -import org.session.libsignal.service.loki.api.fileserver.FileServerAPI; -import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI; -import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol; -import org.session.libsignal.service.loki.utilities.mentions.MentionsManager; -import org.session.libsignal.utilities.logging.Log; import org.signal.aesgcmprovider.AesGcmProvider; import org.thoughtcrime.securesms.components.TypingStatusSender; -import org.session.libsession.utilities.IdentityKeyUtil; -import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.dependencies.InjectableType; diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt index 4fc31efa09..a9325a5527 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki.database import android.content.ContentValues import android.content.Context +import org.session.libsession.utilities.IdentityKeyUtil import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.libsignal.ecc.DjbECPrivateKey import org.session.libsignal.libsignal.ecc.DjbECPublicKey @@ -11,10 +12,8 @@ import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol import org.session.libsignal.service.loki.utilities.PublicKeyValidation import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.toHexString -import org.session.libsession.utilities.IdentityKeyUtil import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.logging.Log -import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.utilities.* From 2460afd1a87e4abb4b5873db386fc6bda18d1701 Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 18 Mar 2021 13:36:56 +1100 Subject: [PATCH 16/51] feat: call resume message send on app create --- .../java/org/thoughtcrime/securesms/ApplicationContext.java | 2 ++ .../main/java/org/session/libsession/messaging/jobs/JobQueue.kt | 1 + 2 files changed, 3 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 623c140474..8a4d284162 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -32,6 +32,7 @@ import androidx.multidex.MultiDexApplication; import org.conscrypt.Conscrypt; import org.session.libsession.messaging.MessagingConfiguration; import org.session.libsession.messaging.avatars.AvatarHelper; +import org.session.libsession.messaging.jobs.JobQueue; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.messaging.sending_receiving.pollers.Poller; import org.session.libsession.messaging.threads.Address; @@ -344,6 +345,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc .setJobStorage(new FastJobStorage(DatabaseFactory.getJobDatabase(this))) .setDependencyInjector(this) .build()); + JobQueue.getShared().resumePendingJobs(); } private void initializeDependencyInjection() { diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index 874ce6e0cc..96b00c8397 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -35,6 +35,7 @@ class JobQueue : JobDelegate { } companion object { + @JvmStatic val shared: JobQueue by lazy { JobQueue() } } From 8ee58459dd5d466ba64148dd3caec47969c143c8 Mon Sep 17 00:00:00 2001 From: jubb Date: Fri, 19 Mar 2021 17:08:31 +1100 Subject: [PATCH 17/51] feat: use new closed and open group pollers --- .../securesms/ApplicationContext.java | 9 ++++--- .../securesms/database/Storage.kt | 24 +++++++++---------- .../loki/api/BackgroundPollWorker.kt | 10 ++++---- .../securesms/loki/api/PublicChatManager.kt | 23 ++++++++++-------- .../libsession/messaging/StorageProtocol.kt | 3 +-- .../sending_receiving/MessageReceiver.kt | 5 ++-- .../MessageReceiverHandler.kt | 2 +- .../pollers/ClosedGroupPoller.kt | 10 ++++---- 8 files changed, 42 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 8a4d284162..b39dd3c5b0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -34,6 +34,7 @@ import org.session.libsession.messaging.MessagingConfiguration; import org.session.libsession.messaging.avatars.AvatarHelper; import org.session.libsession.messaging.jobs.JobQueue; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; +import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPoller; import org.session.libsession.messaging.sending_receiving.pollers.Poller; import org.session.libsession.messaging.threads.Address; import org.session.libsession.snode.SnodeConfiguration; @@ -69,7 +70,6 @@ import org.thoughtcrime.securesms.logging.PersistentLogger; import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger; import org.thoughtcrime.securesms.loki.activities.HomeActivity; import org.thoughtcrime.securesms.loki.api.BackgroundPollWorker; -import org.thoughtcrime.securesms.loki.api.ClosedGroupPoller; import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager; import org.thoughtcrime.securesms.loki.api.PublicChatManager; import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl; @@ -478,10 +478,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc } LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this); SwarmAPI.Companion.configureIfNeeded(apiDB); - SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster); - poller = new Poller(); - ClosedGroupPoller.Companion.configureIfNeeded(this); - closedGroupPoller = ClosedGroupPoller.Companion.getShared(); + SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster); + poller = new Poller(); + closedGroupPoller = new ClosedGroupPoller(); } public void startPollingIfNeeded() { 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 5501a49bea..400a322dd3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -8,10 +8,7 @@ import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageSendJob -import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage -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.messaging.messages.signal.* import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.opengroups.OpenGroup @@ -23,6 +20,7 @@ import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.GroupRecord import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.GroupUtil +import org.session.libsession.utilities.IdentityKeyUtil import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.preferences.ProfileKeyUtil import org.session.libsignal.libsignal.ecc.ECKeyPair @@ -31,18 +29,13 @@ import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos -import org.session.libsignal.service.loki.api.opengroups.PublicChat import org.session.libsignal.utilities.logging.Log -import org.session.libsession.utilities.IdentityKeyUtil import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities import org.thoughtcrime.securesms.loki.utilities.get import org.thoughtcrime.securesms.loki.utilities.getString -import org.session.libsession.messaging.messages.signal.IncomingMediaMessage -import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage -import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.thoughtcrime.securesms.mms.PartAuthority class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { @@ -104,7 +97,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val senderRecipient = Recipient.from(context, senderAddress, false) val group: Optional = when { openGroupID != null -> Optional.of(SignalServiceGroup(openGroupID.toByteArray(), SignalServiceGroup.GroupType.PUBLIC_CHAT)) - groupPublicKey != null -> Optional.of(SignalServiceGroup(groupPublicKey.toByteArray(), SignalServiceGroup.GroupType.SIGNAL)) + groupPublicKey != null -> { + val doubleEncoded = GroupUtil.doubleEncodeGroupID(groupPublicKey) + Optional.of(SignalServiceGroup(GroupUtil.getDecodedGroupIDAsData(doubleEncoded), SignalServiceGroup.GroupType.SIGNAL)) + } else -> Optional.absent() } val pointerAttachments = attachments.mapNotNull { @@ -453,8 +449,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getLokiAPIDatabase(context).removeAllClosedGroupEncryptionKeyPairs(groupPublicKey) } - override fun getAllOpenGroups(): Map { - return DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats() + override fun getAllOpenGroups(): Map { + return DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().mapValues { (_,chat)-> + OpenGroup(chat.channel, chat.server, chat.displayName, chat.isDeletable) + } } override fun addOpenGroup(server: String, channel: Long) { @@ -481,7 +479,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val recipient = Recipient.from(context, Address.fromSerialized(openGroupID), false) return database.getOrCreateThreadIdFor(recipient) } else if (!groupPublicKey.isNullOrEmpty()) { - val recipient = Recipient.from(context, Address.fromSerialized(groupPublicKey), false) + val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)), false) return database.getOrCreateThreadIdFor(recipient) } else { val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt index 422afcee58..57ff5311a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt @@ -7,12 +7,12 @@ import androidx.work.* import nl.komponents.kovenant.Promise import nl.komponents.kovenant.all import nl.komponents.kovenant.functional.map -import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.jobs.PushContentReceiveJob -import org.session.libsignal.utilities.logging.Log import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.service.api.messages.SignalServiceEnvelope import org.session.libsignal.service.loki.api.SnodeAPI +import org.session.libsignal.utilities.logging.Log +import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.jobs.PushContentReceiveJob import java.util.concurrent.TimeUnit class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Worker(context, params) { @@ -76,8 +76,8 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor promises.add(privateChatsPromise) // Closed groups - ClosedGroupPoller.configureIfNeeded(context) - promises.addAll(ClosedGroupPoller.shared.pollOnce()) +// ClosedGroupPoller.configureIfNeeded(context) +// promises.addAll(ClosedGroupPoller.shared.pollOnce()) // Open Groups val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { it.value } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt index edfcbe6526..aca0b020f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt @@ -5,20 +5,22 @@ import android.database.ContentObserver import android.graphics.Bitmap import android.text.TextUtils import androidx.annotation.WorkerThread +import org.session.libsession.messaging.MessagingConfiguration +import org.session.libsession.messaging.opengroups.OpenGroup +import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.Util +import org.session.libsignal.service.loki.api.opengroups.PublicChat +import org.session.libsignal.service.loki.api.opengroups.PublicChatInfo import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.util.BitmapUtil -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.Util -import org.session.libsignal.service.loki.api.opengroups.PublicChatInfo -import org.session.libsignal.service.loki.api.opengroups.PublicChat -import kotlin.jvm.Throws class PublicChatManager(private val context: Context) { - private var chats = mutableMapOf() - private val pollers = mutableMapOf() + private var chats = mutableMapOf() + private val pollers = mutableMapOf() private val observers = mutableMapOf() private var isPolling = false @@ -35,7 +37,7 @@ class PublicChatManager(private val context: Context) { public fun markAllAsNotCaughtUp() { refreshChatsAndPollers() for ((threadID, chat) in chats) { - val poller = pollers[threadID] ?: PublicChatPoller(context, chat) + val poller = pollers[threadID] ?: OpenGroupPoller(chat) poller.isCaughtUp = false } } @@ -44,7 +46,7 @@ class PublicChatManager(private val context: Context) { refreshChatsAndPollers() for ((threadId, chat) in chats) { - val poller = pollers[threadId] ?: PublicChatPoller(context, chat) + val poller = pollers[threadId] ?: OpenGroupPoller(chat) poller.startIfNeeded() listenToThreadDeletion(threadId) if (!pollers.containsKey(threadId)) { pollers[threadId] = poller } @@ -109,7 +111,8 @@ class PublicChatManager(private val context: Context) { } private fun refreshChatsAndPollers() { - val chatsInDB = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats() + val storage = MessagingConfiguration.shared.storage + val chatsInDB = storage.getAllOpenGroups() val removedChatThreadIds = chats.keys.filter { !chatsInDB.keys.contains(it) } removedChatThreadIds.forEach { pollers.remove(it)?.stop() } diff --git a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt index d6306b29b7..967759071c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -20,7 +20,6 @@ import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos -import org.session.libsignal.service.loki.api.opengroups.PublicChat interface StorageProtocol { @@ -56,7 +55,7 @@ interface StorageProtocol { // Open Groups fun getOpenGroup(threadID: String): OpenGroup? fun getThreadID(openGroupID: String): String? - fun getAllOpenGroups(): Map + fun getAllOpenGroups(): Map fun addOpenGroup(server: String, channel: Long) fun setOpenGroupServerMessageID(messageID: Long, serverID: Long) fun getQuoteServerID(quoteID: Long, publicKey: String): Long? diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index c367b5182e..b8a30590e3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -4,6 +4,7 @@ import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.* import org.session.libsession.messaging.messages.visible.VisibleMessage +import org.session.libsession.utilities.GroupUtil import org.session.libsignal.service.internal.push.PushTransportDetails import org.session.libsignal.service.internal.push.SignalServiceProtos @@ -50,7 +51,7 @@ object MessageReceiver { // If the message failed to process the first time around we retry it later (if the error is retryable). In this case the timestamp // will already be in the database but we don't want to treat the message as a duplicate. The isRetry flag is a simple workaround // for this issue. - if (storage.isMessageDuplicated(envelope.timestamp, envelope.source) && !isRetry) throw Error.DuplicateMessage + if (storage.isMessageDuplicated(envelope.timestamp, GroupUtil.doubleEncodeGroupID(envelope.source)) && !isRetry) throw Error.DuplicateMessage storage.addReceivedMessageTimestamp(envelope.timestamp) // Decrypt the contents val ciphertext = envelope.content ?: throw Error.NoData @@ -70,7 +71,7 @@ object MessageReceiver { } SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT -> { val hexEncodedGroupPublicKey = envelope.source - if (hexEncodedGroupPublicKey == null || MessagingConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey)) { + if (hexEncodedGroupPublicKey == null || !MessagingConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey)) { throw Error.InvalidGroupPublicKey } val encryptionKeyPairs = MessagingConfiguration.shared.storage.getClosedGroupEncryptionKeyPairs(hexEncodedGroupPublicKey) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index ebd29a37f4..3adc912ea3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -186,8 +186,8 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS } // Parse stickers if needed // Persist the message - val messageID = storage.persist(message, quoteModel, linkPreviews, message.groupPublicKey, openGroupID, attachments) ?: throw MessageReceiver.Error.NoThread message.threadID = threadID + val messageID = storage.persist(message, quoteModel, linkPreviews, message.groupPublicKey, openGroupID, attachments) ?: throw MessageReceiver.Error.NoThread // Parse & persist attachments // Start attachment downloads if needed storage.getAttachmentsForMessage(messageID).forEach { attachment -> diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt index e2ac3c3aee..3c63bdc014 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt @@ -4,17 +4,15 @@ import android.os.Handler import nl.komponents.kovenant.Promise import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map - import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveJob import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.snode.SnodeAPI -import org.session.libsignal.utilities.successBackground - -import org.session.libsignal.utilities.logging.Log -import org.session.libsignal.utilities.Base64 import org.session.libsignal.service.loki.utilities.getRandomElementOrNull +import org.session.libsignal.utilities.Base64 +import org.session.libsignal.utilities.logging.Log +import org.session.libsignal.utilities.successBackground class ClosedGroupPoller { private var isPolling = false @@ -24,7 +22,7 @@ class ClosedGroupPoller { override fun run() { poll() - handler.postDelayed(this, ClosedGroupPoller.pollInterval) + handler.postDelayed(this, pollInterval) } } From 6f2bad9b5974fd9960a6c13e353c474528b59e55 Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 23 Mar 2021 10:00:51 +1100 Subject: [PATCH 18/51] fix: no duplicate group left messages, more efficient closed group polling --- .../securesms/database/Storage.kt | 10 ++++++++ .../libsession/messaging/StorageProtocol.kt | 2 ++ .../sending_receiving/MessageReceiver.kt | 2 ++ .../MessageReceiverHandler.kt | 24 +++++++++++++++++++ .../sending_receiving/MessageSender.kt | 2 +- .../MessageSenderClosedGroup.kt | 3 +++ .../pollers/ClosedGroupPoller.kt | 6 ++++- .../snode/OnionRequestEncryption.kt | 10 ++++---- 8 files changed, 52 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 400a322dd3..59687aa0a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -372,6 +372,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getGroupDatabase(context).create(groupId, title, members, avatar, relay, admins, formationTimestamp) } + override fun isGroupActive(groupPublicKey: String): Boolean { + return DatabaseFactory.getGroupDatabase(context).getGroup(GroupUtil.doubleEncodeGroupID(groupPublicKey)).orNull()?.isActive == true + } + override fun setActive(groupID: String, value: Boolean) { DatabaseFactory.getGroupDatabase(context).setActive(groupID, value) } @@ -433,6 +437,12 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys() } + override fun getAllActiveClosedGroupPublicKeys(): Set { + return DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys().filter { + getGroup(GroupUtil.doubleEncodeGroupID(it))?.isActive == true + }.toSet() + } + override fun addClosedGroupPublicKey(groupPublicKey: String) { DatabaseFactory.getLokiAPIDatabase(context).addClosedGroupPublicKey(groupPublicKey) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt index 967759071c..8a392953d2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -102,11 +102,13 @@ interface StorageProtocol { // Closed Groups fun getGroup(groupID: String): GroupRecord? fun createGroup(groupID: String, title: String?, members: List
, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List
, formationTimestamp: Long) + fun isGroupActive(groupPublicKey: String): Boolean fun setActive(groupID: String, value: Boolean) fun removeMember(groupID: String, member: Address) fun updateMembers(groupID: String, members: List
) // Closed Group fun getAllClosedGroupPublicKeys(): Set + fun getAllActiveClosedGroupPublicKeys(): Set fun addClosedGroupPublicKey(groupPublicKey: String) fun removeClosedGroupPublicKey(groupPublicKey: String) fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index b8a30590e3..6322083a10 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -99,6 +99,8 @@ object MessageReceiver { else -> throw Error.UnknownEnvelopeType } } + // Don't process the envelope any further if the message has been handled already + if (storage.isMessageDuplicated(envelope.timestamp, sender!!) && !isRetry) throw Error.DuplicateMessage // Don't process the envelope any further if the sender is blocked if (isBlock(sender!!)) throw Error.SenderBlocked // Parse the proto diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index 3adc912ea3..79b063e98f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -264,6 +264,10 @@ private fun MessageReceiver.handleClosedGroupUpdated(message: ClosedGroupControl Log.d("Loki", "Ignoring closed group info message for nonexistent group.") return } + if (!group.isActive) { + Log.d("Loki", "Ignoring closed group info message for inactive group") + return + } val oldMembers = group.members.map { it.serialize() } // Check common group update logic if (!isValidGroupUpdate(group, message.sentTimestamp!!, senderPublicKey)) { @@ -312,6 +316,10 @@ private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGr Log.d("Loki", "Ignoring closed group info message for nonexistent group.") return } + if (!group.isActive) { + Log.d("Loki", "Ignoring closed group info message for inactive group") + return + } if (!group.members.map { it.toString() }.contains(senderPublicKey)) { Log.d("Loki", "Ignoring closed group encryption key pair from non-member.") return @@ -345,6 +353,10 @@ private fun MessageReceiver.handleClosedGroupNameChanged(message: ClosedGroupCon Log.d("Loki", "Ignoring closed group info message for nonexistent group.") return } + if (!group.isActive) { + Log.d("Loki", "Ignoring closed group info message for inactive group") + return + } // Check common group update logic if (!isValidGroupUpdate(group, message.sentTimestamp!!, senderPublicKey)) { return @@ -369,6 +381,10 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo Log.d("Loki", "Ignoring closed group info message for nonexistent group.") return } + if (!group.isActive) { + Log.d("Loki", "Ignoring closed group info message for inactive group") + return + } if (!isValidGroupUpdate(group, message.sentTimestamp!!, senderPublicKey)) { return } val name = group.title // Check common group update logic @@ -411,6 +427,10 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup Log.d("Loki", "Ignoring closed group info message for nonexistent group.") return } + if (!group.isActive) { + Log.d("Loki", "Ignoring closed group info message for inactive group") + return + } val name = group.title // Check common group update logic val members = group.members.map { it.serialize() } @@ -460,6 +480,10 @@ private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupCont Log.d("Loki", "Ignoring closed group info message for nonexistent group.") return } + if (!group.isActive) { + Log.d("Loki", "Ignoring closed group info message for inactive group") + return + } val name = group.title // Check common group update logic val members = group.members.map { it.serialize() } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index bfdc69598f..c8bfb2d66f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -337,6 +337,6 @@ object MessageSender { @JvmStatic fun explicitLeave(groupPublicKey: String): Promise { - return leave(groupPublicKey) + return leave(groupPublicKey, false) } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt index 7faf482740..58f8ffe64c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroup.kt @@ -209,6 +209,7 @@ fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Pro val closedGroupControlMessage = ClosedGroupControlMessage(ClosedGroupControlMessage.Kind.MemberLeft()) val sentTime = System.currentTimeMillis() closedGroupControlMessage.sentTimestamp = sentTime + storage.setActive(groupID, false) sendNonDurably(closedGroupControlMessage, Address.fromSerialized(groupID)).success { // Notify the user val infoType = SignalServiceProtos.GroupContext.Type.QUIT @@ -219,6 +220,8 @@ fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Pro // Remove the group private key and unsubscribe from PNs MessageReceiver.disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey) deferred.resolve(Unit) + }.fail { + storage.setActive(groupID, true) } } return deferred.promise diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt index 3c63bdc014..b78c382658 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt @@ -59,7 +59,7 @@ class ClosedGroupPoller { // region Private API private fun poll(): List> { if (!isPolling) { return listOf() } - val publicKeys = MessagingConfiguration.shared.storage.getAllClosedGroupPublicKeys() + val publicKeys = MessagingConfiguration.shared.storage.getAllActiveClosedGroupPublicKeys() return publicKeys.map { publicKey -> val promise = SnodeAPI.getSwarm(publicKey).bind { swarm -> val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure @@ -67,6 +67,10 @@ class ClosedGroupPoller { SnodeAPI.getRawMessages(snode, publicKey).map {SnodeAPI.parseRawMessagesResponse(it, snode, publicKey) } } promise.successBackground { messages -> + if (!MessagingConfiguration.shared.storage.isGroupActive(publicKey)) { + // ignore inactive group's messages + return@successBackground + } if (messages.isNotEmpty()) { Log.d("Loki", "Received ${messages.count()} new message(s) in closed group with public key: $publicKey.") } diff --git a/libsession/src/main/java/org/session/libsession/snode/OnionRequestEncryption.kt b/libsession/src/main/java/org/session/libsession/snode/OnionRequestEncryption.kt index c0e1e77b25..77de1c783c 100644 --- a/libsession/src/main/java/org/session/libsession/snode/OnionRequestEncryption.kt +++ b/libsession/src/main/java/org/session/libsession/snode/OnionRequestEncryption.kt @@ -2,11 +2,11 @@ package org.session.libsession.snode import nl.komponents.kovenant.Promise import nl.komponents.kovenant.deferred -import org.session.libsignal.utilities.JsonUtil -import org.session.libsession.utilities.AESGCM.EncryptionResult import org.session.libsession.utilities.AESGCM -import org.session.libsignal.utilities.ThreadUtils +import org.session.libsession.utilities.AESGCM.EncryptionResult import org.session.libsignal.service.loki.utilities.toHexString +import org.session.libsignal.utilities.JsonUtil +import org.session.libsignal.utilities.ThreadUtils import java.nio.Buffer import java.nio.ByteBuffer import java.nio.ByteOrder @@ -62,7 +62,7 @@ object OnionRequestEncryption { */ internal fun encryptHop(lhs: OnionRequestAPI.Destination, rhs: OnionRequestAPI.Destination, previousEncryptionResult: EncryptionResult): Promise { val deferred = deferred() - Thread { + ThreadUtils.queue { try { val payload: MutableMap when (rhs) { @@ -89,7 +89,7 @@ object OnionRequestEncryption { } catch (exception: Exception) { deferred.reject(exception) } - }.start() + } return deferred.promise } } From e378d11692e3439ed22cf179e8226a0bc9e5522e Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 23 Mar 2021 10:11:10 +1100 Subject: [PATCH 19/51] few little fixes --- .../messages/control/ClosedGroupControlMessage.kt | 3 +-- .../messages/control/DataExtractionNotification.kt | 12 +++++------- .../messaging/messages/control/ReadReceipt.kt | 4 ++-- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt index d7e440fe98..70c75ddce3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ClosedGroupControlMessage.kt @@ -38,7 +38,7 @@ class ClosedGroupControlMessage() : ControlMessage() { object MemberLeft : Kind() object EncryptionKeyPairRequest: Kind() - val description: String = run { + val description: String = when(this) { is New -> "new" is Update -> "update" @@ -49,7 +49,6 @@ class ClosedGroupControlMessage() : ControlMessage() { MemberLeft -> "memberLeft" EncryptionKeyPairRequest -> "encryptionKeyPairRequest" } - } } companion object { diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt index a612584af8..1888f66a49 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt @@ -14,12 +14,11 @@ class DataExtractionNotification(): ControlMessage() { class Screenshot() : Kind() class MediaSaved(val timestanp: Long) : Kind() - val description: String = run { + val description: String = when(this) { is Screenshot -> "screenshot" is MediaSaved -> "mediaSaved" } - } } companion object { @@ -27,12 +26,11 @@ class DataExtractionNotification(): ControlMessage() { fun fromProto(proto: SignalServiceProtos.Content): DataExtractionNotification? { val dataExtractionNotification = proto.dataExtractionNotification ?: return null - val kind: Kind - when(dataExtractionNotification.type) { - SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT -> kind = Kind.Screenshot() + val kind: Kind = when(dataExtractionNotification.type) { + SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT -> Kind.Screenshot() SignalServiceProtos.DataExtractionNotification.Type.MEDIA_SAVED -> { - val timestamp = if (dataExtractionNotification.hasTimestamp()) dataExtractionNotification.timestamp else 0 - kind = Kind.MediaSaved(timestamp) + val timestamp = if (dataExtractionNotification.hasTimestamp()) dataExtractionNotification.timestamp else return null + Kind.MediaSaved(timestamp) } } return DataExtractionNotification(kind) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt index 0ee1f0acf9..b430f09c41 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ReadReceipt.kt @@ -35,7 +35,7 @@ class ReadReceipt() : ControlMessage() { override fun toProto(): SignalServiceProtos.Content? { val timestamps = timestamps if (timestamps == null) { - Log.w(ExpirationTimerUpdate.TAG, "Couldn't construct read receipt proto from: $this") + Log.w(TAG, "Couldn't construct read receipt proto from: $this") return null } val receiptProto = SignalServiceProtos.ReceiptMessage.newBuilder() @@ -46,7 +46,7 @@ class ReadReceipt() : ControlMessage() { contentProto.receiptMessage = receiptProto.build() return contentProto.build() } catch (e: Exception) { - Log.w(ExpirationTimerUpdate.TAG, "Couldn't construct read receipt proto from: $this") + Log.w(TAG, "Couldn't construct read receipt proto from: $this") return null } } From 0fa62d1b2a072629d0b366683a80700c296eba83 Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 23 Mar 2021 10:35:47 +1100 Subject: [PATCH 20/51] fix: only set syncTarget if it's included and not empty --- .../libsession/messaging/messages/visible/VisibleMessage.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt index b7f37af478..0b2c4b59e8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt @@ -29,7 +29,9 @@ class VisibleMessage : Message() { fun fromProto(proto: SignalServiceProtos.Content): VisibleMessage? { val dataMessage = if (proto.hasDataMessage()) proto.dataMessage else return null val result = VisibleMessage() - result.syncTarget = dataMessage.syncTarget + if (dataMessage.hasSyncTarget()) { + result.syncTarget = dataMessage.syncTarget + } result.text = dataMessage.body // Attachments are handled in MessageReceiver val quoteProto = if (dataMessage.hasQuote()) dataMessage.quote else null From 0bea7fa56d1d1349f32992719bcbeabe5354bbd1 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 23 Mar 2021 10:43:32 +1100 Subject: [PATCH 21/51] clean --- .../org/session/libsession/messaging/jobs/MessageReceiveJob.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt index d11bc2d3d2..4ce46b4577 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt @@ -15,7 +15,6 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val // Settings override val maxFailureCount: Int = 10 companion object { - val TAG = MessageReceiveJob::class.simpleName val KEY: String = "MessageReceiveJob" From de0f9a26df76ac7c0a1635cae6a88f39a0bef966 Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 23 Mar 2021 11:12:37 +1100 Subject: [PATCH 22/51] refactor: remove old pollers from BackgroundPollWorker.kt and deleted unused pollers --- .../loki/api/BackgroundPollWorker.kt | 21 +- .../securesms/loki/api/ClosedGroupPoller.kt | 91 ------- .../securesms/loki/api/PublicChatPoller.kt | 238 ------------------ 3 files changed, 12 insertions(+), 338 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/loki/api/ClosedGroupPoller.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt index 57ff5311a1..8476fae3e1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt @@ -7,12 +7,14 @@ import androidx.work.* import nl.komponents.kovenant.Promise import nl.komponents.kovenant.all import nl.komponents.kovenant.functional.map +import org.session.libsession.messaging.jobs.MessageReceiveJob +import org.session.libsession.messaging.opengroups.OpenGroup +import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.service.api.messages.SignalServiceEnvelope import org.session.libsignal.service.loki.api.SnodeAPI import org.session.libsignal.utilities.logging.Log +import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.jobs.PushContentReceiveJob import java.util.concurrent.TimeUnit class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Worker(context, params) { @@ -69,20 +71,21 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor // Private chats val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! val privateChatsPromise = SnodeAPI.shared.getMessages(userPublicKey).map { envelopes -> - envelopes.forEach { - PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false) + envelopes.map { envelope -> + MessageReceiveJob(envelope.toByteArray(), false).executeAsync() } } - promises.add(privateChatsPromise) + promises.addAll(privateChatsPromise.get()) // Closed groups -// ClosedGroupPoller.configureIfNeeded(context) -// promises.addAll(ClosedGroupPoller.shared.pollOnce()) + promises.addAll(ApplicationContext.getInstance(context).closedGroupPoller.pollOnce()) // Open Groups - val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { it.value } + val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { (_,chat)-> + OpenGroup(chat.channel, chat.server, chat.displayName, chat.isDeletable) + } for (openGroup in openGroups) { - val poller = PublicChatPoller(context, openGroup) + val poller = OpenGroupPoller(openGroup) promises.add(poller.pollForNewMessages()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/ClosedGroupPoller.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/ClosedGroupPoller.kt deleted file mode 100644 index 5264a1892a..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/ClosedGroupPoller.kt +++ /dev/null @@ -1,91 +0,0 @@ -package org.thoughtcrime.securesms.loki.api - -import android.content.Context -import android.os.Handler -import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.functional.bind -import nl.komponents.kovenant.functional.map -import org.thoughtcrime.securesms.jobs.PushContentReceiveJob -import org.session.libsignal.utilities.logging.Log -import org.session.libsignal.utilities.successBackground -import org.session.libsignal.service.api.messages.SignalServiceEnvelope -import org.session.libsignal.service.loki.api.SnodeAPI -import org.session.libsignal.service.loki.api.SwarmAPI -import org.session.libsignal.service.loki.utilities.getRandomElementOrNull -import org.thoughtcrime.securesms.database.DatabaseFactory - -class ClosedGroupPoller private constructor(private val context: Context) { - private var isPolling = false - private val handler: Handler by lazy { Handler() } - - private val task = object : Runnable { - - override fun run() { - poll() - handler.postDelayed(this, pollInterval) - } - } - - // region Settings - companion object { - private val pollInterval: Long = 4 * 1000 - - public lateinit var shared: ClosedGroupPoller - - public fun configureIfNeeded(context: Context) { - if (::shared.isInitialized) { return; } - shared = ClosedGroupPoller(context) - } - } - // endregion - - // region Error - class InsufficientSnodesException() : Exception("No snodes left to poll.") - class PollingCanceledException() : Exception("Polling canceled.") - // endregion - - // region Public API - fun startIfNeeded() { - if (isPolling) { return } - isPolling = true - task.run() - } - - fun pollOnce(): List> { - if (isPolling) { return listOf() } - isPolling = true - return poll() - } - - fun stopIfNeeded() { - isPolling = false - handler.removeCallbacks(task) - } - // endregion - - // region Private API - private fun poll(): List> { - if (!isPolling) { return listOf() } - val publicKeys = DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys() - return publicKeys.map { publicKey -> - val promise = SwarmAPI.shared.getSwarm(publicKey).bind { swarm -> - val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure - if (!isPolling) { throw PollingCanceledException() } - SnodeAPI.shared.getRawMessages(snode, publicKey).map {SnodeAPI.shared.parseRawMessagesResponse(it, snode, publicKey) } - } - promise.successBackground { messages -> - if (messages.isNotEmpty()) { - Log.d("Loki", "Received ${messages.count()} new message(s) in closed group with public key: $publicKey.") - } - messages.forEach { - PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false) - } - } - promise.fail { - Log.d("Loki", "Polling failed for closed group with public key: $publicKey due to error: $it.") - } - promise.map { Unit } - } - } - // endregion -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt deleted file mode 100644 index 4b69f0019f..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt +++ /dev/null @@ -1,238 +0,0 @@ -package org.thoughtcrime.securesms.loki.api - -import android.content.Context -import android.os.Handler -import org.session.libsignal.utilities.logging.Log -import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.functional.bind -import nl.komponents.kovenant.functional.map -import org.thoughtcrime.securesms.ApplicationContext -import org.session.libsession.utilities.IdentityKeyUtil -import org.session.libsession.messaging.threads.Address -import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.jobs.PushDecryptJob -import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob -import org.session.libsession.messaging.threads.recipients.Recipient -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.libsignal.util.guava.Optional -import org.session.libsignal.utilities.successBackground -import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer -import org.session.libsignal.service.api.messages.SignalServiceContent -import org.session.libsignal.service.api.messages.SignalServiceDataMessage -import org.session.libsignal.service.api.messages.SignalServiceGroup -import org.session.libsignal.service.api.push.SignalServiceAddress -import org.session.libsignal.service.loki.api.fileserver.FileServerAPI -import org.session.libsignal.service.loki.api.opengroups.PublicChat -import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI -import org.session.libsignal.service.loki.api.opengroups.PublicChatMessage -import java.security.MessageDigest -import java.util.* - -class PublicChatPoller(private val context: Context, private val group: PublicChat) { - private val handler by lazy { Handler() } - private var hasStarted = false - private var isPollOngoing = false - public var isCaughtUp = false - - // region Convenience - private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)!! - private var displayNameUpdatees = setOf() - - private val api: PublicChatAPI - get() = { - val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() - val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context) - val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context) - val openGroupDatabase = DatabaseFactory.getGroupDatabase(context) - PublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase, openGroupDatabase) - }() - // endregion - - // region Tasks - private val pollForNewMessagesTask = object : Runnable { - - override fun run() { - pollForNewMessages() - handler.postDelayed(this, pollForNewMessagesInterval) - } - } - - private val pollForDeletedMessagesTask = object : Runnable { - - override fun run() { - pollForDeletedMessages() - handler.postDelayed(this, pollForDeletedMessagesInterval) - } - } - - private val pollForModeratorsTask = object : Runnable { - - override fun run() { - pollForModerators() - handler.postDelayed(this, pollForModeratorsInterval) - } - } - - private val pollForDisplayNamesTask = object : Runnable { - - override fun run() { - pollForDisplayNames() - handler.postDelayed(this, pollForDisplayNamesInterval) - } - } - // endregion - - // region Settings - companion object { - private val pollForNewMessagesInterval: Long = 4 * 1000 - private val pollForDeletedMessagesInterval: Long = 60 * 1000 - private val pollForModeratorsInterval: Long = 10 * 60 * 1000 - private val pollForDisplayNamesInterval: Long = 60 * 1000 - } - // endregion - - // region Lifecycle - fun startIfNeeded() { - if (hasStarted) return - pollForNewMessagesTask.run() - pollForDeletedMessagesTask.run() - pollForModeratorsTask.run() - pollForDisplayNamesTask.run() - hasStarted = true - } - - fun stop() { - handler.removeCallbacks(pollForNewMessagesTask) - handler.removeCallbacks(pollForDeletedMessagesTask) - handler.removeCallbacks(pollForModeratorsTask) - handler.removeCallbacks(pollForDisplayNamesTask) - hasStarted = false - } - // endregion - - // region Polling - private fun getDataMessage(message: PublicChatMessage): SignalServiceDataMessage { - val id = group.id.toByteArray() - val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.PUBLIC_CHAT, null, null, null, null) - val quote = if (message.quote != null) { - SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteePublicKey), message.quote!!.quotedMessageBody, listOf()) - } else { - null - } - val attachments = message.attachments.mapNotNull { attachment -> - if (attachment.kind != PublicChatMessage.Attachment.Kind.Attachment) { return@mapNotNull null } - SignalServiceAttachmentPointer( - attachment.serverID, - attachment.contentType, - ByteArray(0), - Optional.of(attachment.size), - Optional.absent(), - attachment.width, attachment.height, - Optional.absent(), - Optional.of(attachment.fileName), - false, - Optional.fromNullable(attachment.caption), - attachment.url) - } - val linkPreview = message.attachments.firstOrNull { it.kind == PublicChatMessage.Attachment.Kind.LinkPreview } - val signalLinkPreviews = mutableListOf() - if (linkPreview != null) { - val attachment = SignalServiceAttachmentPointer( - linkPreview.serverID, - linkPreview.contentType, - ByteArray(0), - Optional.of(linkPreview.size), - Optional.absent(), - linkPreview.width, linkPreview.height, - Optional.absent(), - Optional.of(linkPreview.fileName), - false, - Optional.fromNullable(linkPreview.caption), - linkPreview.url) - signalLinkPreviews.add(SignalServiceDataMessage.Preview(linkPreview.linkPreviewURL!!, linkPreview.linkPreviewTitle!!, Optional.of(attachment))) - } - val body = if (message.body == message.timestamp.toString()) "" else message.body // Workaround for the fact that the back-end doesn't accept messages without a body - val syncTarget = if (message.senderPublicKey == userHexEncodedPublicKey) group.id else null - return SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, 0, false, null, quote, null, signalLinkPreviews, null, syncTarget) - } - - fun pollForNewMessages(): Promise { - if (isPollOngoing) { return Promise.of(Unit) } - isPollOngoing = true - val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() - val apiDB = DatabaseFactory.getLokiAPIDatabase(context) - FileServerAPI.configure(userHexEncodedPublicKey, userPrivateKey, apiDB) - // Kovenant propagates a context to chained promises, so LokiPublicChatAPI.sharedContext should be used for all of the below - val promise = api.getMessages(group.channel, group.server).bind(PublicChatAPI.sharedContext) { messages -> - Promise.of(messages) - } - promise.successBackground { messages -> - // Process messages in the background - messages.forEach { message -> - // If the sender of the current message is not a slave device, set the display name in the database - val senderDisplayName = "${message.displayName} (...${message.senderPublicKey.takeLast(8)})" - DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.senderPublicKey, senderDisplayName) - val senderHexEncodedPublicKey = message.senderPublicKey - val serviceDataMessage = getDataMessage(message) - val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.serverTimestamp, false) - if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { - PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) - } else { - PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) - } - // Update profile picture if needed - val senderAsRecipient = Recipient.from(context, Address.fromSerialized(senderHexEncodedPublicKey), false) - if (message.profilePicture != null && message.profilePicture!!.url.isNotEmpty()) { - val profileKey = message.profilePicture!!.profileKey - val url = message.profilePicture!!.url - if (senderAsRecipient.profileKey == null || !MessageDigest.isEqual(senderAsRecipient.profileKey, profileKey)) { - val database = DatabaseFactory.getRecipientDatabase(context) - database.setProfileKey(senderAsRecipient, profileKey) - ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderAsRecipient, url)) - } - } - } - isCaughtUp = true - isPollOngoing = false - } - promise.fail { - Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.") - isPollOngoing = false - } - return promise.map { Unit } - } - - private fun pollForDisplayNames() { - if (displayNameUpdatees.isEmpty()) { return } - val hexEncodedPublicKeys = displayNameUpdatees - displayNameUpdatees = setOf() - api.getDisplayNames(hexEncodedPublicKeys, group.server).successBackground { mapping -> - for (pair in mapping.entries) { - val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})" - DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, pair.key, senderDisplayName) - } - }.fail { - displayNameUpdatees = displayNameUpdatees.union(hexEncodedPublicKeys) - } - } - - private fun pollForDeletedMessages() { - api.getDeletedMessageServerIDs(group.channel, group.server).success { deletedMessageServerIDs -> - val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context) - val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { lokiMessageDatabase.getMessageID(it) } - val smsMessageDatabase = DatabaseFactory.getSmsDatabase(context) - val mmsMessageDatabase = DatabaseFactory.getMmsDatabase(context) - deletedMessageIDs.forEach { - smsMessageDatabase.deleteMessage(it) - mmsMessageDatabase.delete(it) - } - }.fail { - Log.d("Loki", "Failed to get deleted messages for group chat with ID: ${group.channel} on server: ${group.server}.") - } - } - - private fun pollForModerators() { - api.getModerators(group.channel, group.server) - } - // endregion -} \ No newline at end of file From a60ec8aaef6131a02b77e01fc2b3c09f0fcea5ea Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 23 Mar 2021 11:15:25 +1100 Subject: [PATCH 23/51] fix: fix compile error --- .../java/org/thoughtcrime/securesms/database/Storage.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 578f4d0164..8c47ad6728 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -9,6 +9,7 @@ import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.messages.signal.* +import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.opengroups.OpenGroup @@ -37,11 +38,6 @@ import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities import org.thoughtcrime.securesms.loki.utilities.get import org.thoughtcrime.securesms.loki.utilities.getString import org.thoughtcrime.securesms.mms.PartAuthority -import org.session.libsession.messaging.messages.signal.IncomingGroupMessage -import org.session.libsession.messaging.messages.signal.IncomingTextMessage -import org.session.libsession.messaging.messages.signal.OutgoingTextMessage -import org.session.libsession.utilities.preferences.ProfileKeyUtil -import org.session.libsignal.service.loki.utilities.prettifiedDescription class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { override fun getUserPublicKey(): String? { From 1c1ce1424b07763dfebc6157c73a5d0b63fee348 Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 23 Mar 2021 11:39:14 +1100 Subject: [PATCH 24/51] fix: replace elvis on optional proto object with protobuf has flag check --- .../control/DataExtractionNotification.kt | 6 +- .../libsignal/service/loki/api/Poller.kt | 95 ------------------- 2 files changed, 2 insertions(+), 99 deletions(-) delete mode 100644 libsignal/src/main/java/org/session/libsignal/service/loki/api/Poller.kt diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt index 1888f66a49..0526fbec0f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/DataExtractionNotification.kt @@ -1,10 +1,7 @@ package org.session.libsession.messaging.messages.control -import com.google.protobuf.ByteString -import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.utilities.logging.Log -import java.lang.Exception class DataExtractionNotification(): ControlMessage() { var kind: Kind? = null @@ -25,7 +22,8 @@ class DataExtractionNotification(): ControlMessage() { const val TAG = "DataExtractionNotification" fun fromProto(proto: SignalServiceProtos.Content): DataExtractionNotification? { - val dataExtractionNotification = proto.dataExtractionNotification ?: return null + if (!proto.hasDataExtractionNotification()) return null + val dataExtractionNotification = proto.dataExtractionNotification!! val kind: Kind = when(dataExtractionNotification.type) { SignalServiceProtos.DataExtractionNotification.Type.SCREENSHOT -> Kind.Screenshot() SignalServiceProtos.DataExtractionNotification.Type.MEDIA_SAVED -> { diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/api/Poller.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/api/Poller.kt deleted file mode 100644 index 38faea7340..0000000000 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/api/Poller.kt +++ /dev/null @@ -1,95 +0,0 @@ -package org.session.libsignal.service.loki.api - -import nl.komponents.kovenant.* -import nl.komponents.kovenant.functional.bind -import org.session.libsignal.utilities.logging.Log -import org.session.libsignal.service.internal.push.SignalServiceProtos -import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol -import java.security.SecureRandom -import java.util.* - -private class PromiseCanceledException : Exception("Promise canceled.") - -class Poller(public var userPublicKey: String, private val database: LokiAPIDatabaseProtocol, private val onMessagesReceived: (List) -> Unit) { - private var hasStarted: Boolean = false - private val usedSnodes: MutableSet = mutableSetOf() - public var isCaughtUp = false - - // region Settings - companion object { - private val retryInterval: Long = 1 * 1000 - } - // endregion - - // region Public API - fun startIfNeeded() { - if (hasStarted) { return } - Log.d("Loki", "Started polling.") - hasStarted = true - setUpPolling() - } - - fun stopIfNeeded() { - Log.d("Loki", "Stopped polling.") - hasStarted = false - usedSnodes.clear() - } - // endregion - - // region Private API - private fun setUpPolling() { - if (!hasStarted) { return; } - val thread = Thread.currentThread() - SwarmAPI.shared.getSwarm(userPublicKey).bind(SnodeAPI.messagePollingContext) { - usedSnodes.clear() - val deferred = deferred(SnodeAPI.messagePollingContext) - pollNextSnode(deferred) - deferred.promise - }.always { - Timer().schedule(object : TimerTask() { - - override fun run() { - thread.run { setUpPolling() } - } - }, retryInterval) - } - } - - private fun pollNextSnode(deferred: Deferred) { - val swarm = database.getSwarm(userPublicKey) ?: setOf() - val unusedSnodes = swarm.subtract(usedSnodes) - if (unusedSnodes.isNotEmpty()) { - val index = SecureRandom().nextInt(unusedSnodes.size) - val nextSnode = unusedSnodes.elementAt(index) - usedSnodes.add(nextSnode) - Log.d("Loki", "Polling $nextSnode.") - poll(nextSnode, deferred).fail { exception -> - if (exception is PromiseCanceledException) { - Log.d("Loki", "Polling $nextSnode canceled.") - } else { - Log.d("Loki", "Polling $nextSnode failed; dropping it and switching to next snode.") - SwarmAPI.shared.dropSnodeFromSwarmIfNeeded(nextSnode, userPublicKey) - pollNextSnode(deferred) - } - } - } else { - isCaughtUp = true - deferred.resolve() - } - } - - private fun poll(snode: Snode, deferred: Deferred): Promise { - if (!hasStarted) { return Promise.ofFail(PromiseCanceledException()) } - return SnodeAPI.shared.getRawMessages(snode, userPublicKey).bind(SnodeAPI.messagePollingContext) { rawResponse -> - isCaughtUp = true - if (deferred.promise.isDone()) { - task { Unit } // The long polling connection has been canceled; don't recurse - } else { - val messages = SnodeAPI.shared.parseRawMessagesResponse(rawResponse, snode, userPublicKey) - onMessagesReceived(messages) - poll(snode, deferred) - } - } - } - // endregion -} From fad8cd5ba67ca345d12d4814f2d28c48056c495c Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 23 Mar 2021 15:56:15 +1100 Subject: [PATCH 25/51] fix: changing the way received timestamp handling happens --- .../java/org/thoughtcrime/securesms/database/Storage.kt | 7 +------ .../messaging/sending_receiving/MessageReceiver.kt | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 8c47ad6728..610bde1bed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -274,12 +274,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } override fun isMessageDuplicated(timestamp: Long, sender: String): Boolean { - val database = DatabaseFactory.getMmsSmsDatabase(context) - return if (sender.isEmpty()) { - database.getMessageForTimestamp(timestamp) != null - } else { - database.getMessageFor(timestamp, sender) != null - } + return getReceivedMessageTimestamps().contains(timestamp) } override fun setUserCount(group: Long, server: String, newValue: Int) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index 98865851f6..89cc060ec1 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -52,7 +52,6 @@ object MessageReceiver { // will already be in the database but we don't want to treat the message as a duplicate. The isRetry flag is a simple workaround // for this issue. if (storage.isMessageDuplicated(envelope.timestamp, GroupUtil.doubleEncodeGroupID(envelope.source)) && !isRetry) throw Error.DuplicateMessage - storage.addReceivedMessageTimestamp(envelope.timestamp) // Decrypt the contents val ciphertext = envelope.content ?: throw Error.NoData var plaintext: ByteArray? = null @@ -101,6 +100,7 @@ object MessageReceiver { } // Don't process the envelope any further if the message has been handled already if (storage.isMessageDuplicated(envelope.timestamp, sender!!) && !isRetry) throw Error.DuplicateMessage + storage.addReceivedMessageTimestamp(envelope.timestamp) // Don't process the envelope any further if the sender is blocked if (isBlock(sender!!)) throw Error.SenderBlocked // Parse the proto From 611b272493ffd4b0bcfaf54b21f8a5db03907fb0 Mon Sep 17 00:00:00 2001 From: jubb Date: Wed, 24 Mar 2021 17:17:01 +1100 Subject: [PATCH 26/51] fix: attachments for quotes no longer doubled up --- .../attachments/DatabaseAttachmentProvider.kt | 14 +++++----- .../database/AttachmentDatabase.java | 26 +++++++++---------- .../securesms/database/MmsSmsDatabase.java | 12 ++++----- .../database/MessageDataProvider.kt | 4 +-- .../messages/signal/IncomingMediaMessage.java | 2 +- .../MessageReceiverHandler.kt | 4 +-- 6 files changed, 29 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt index b232a1d3e6..105cabf37b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -68,17 +68,17 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) override fun getMessageForQuote(timestamp: Long, author: Address): Long? { val messagingDatabase = DatabaseFactory.getMmsSmsDatabase(context) - return messagingDatabase.getMessageFor(timestamp, author)?.id + val message = messagingDatabase.getMessageFor(timestamp, author) + return message?.id } - override fun getAttachmentsAndLinkPreviewFor(messageID: Long): List { - val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context) - return attachmentDatabase.getAttachmentsForMessage(messageID) + override fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List { + return DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(mmsId) } - override fun getMessageBodyFor(messageID: Long): String { - val messagingDatabase = DatabaseFactory.getSmsDatabase(context) - return messagingDatabase.getMessage(messageID).body + override fun getMessageBodyFor(timestamp: Long, author: String): String { + val messagingDatabase = DatabaseFactory.getMmsSmsDatabase(context) + return messagingDatabase.getMessageFor(timestamp, author)!!.body } override fun getAttachmentIDsFor(messageID: Long): List { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java index 887ea913bc..b69f879993 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -37,32 +37,26 @@ import net.sqlcipher.database.SQLiteDatabase; import org.json.JSONArray; import org.json.JSONException; - +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.AttachmentTransferProgress; +import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; +import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachmentAudioExtras; import org.session.libsession.utilities.MediaTypes; +import org.session.libsession.utilities.Util; +import org.session.libsignal.utilities.JsonUtil; +import org.session.libsignal.utilities.externalstorage.ExternalStorageUtil; import org.session.libsignal.utilities.logging.Log; - import org.thoughtcrime.securesms.crypto.AttachmentSecret; import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream; import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; - -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; -import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachmentAudioExtras; - -import org.session.libsignal.utilities.JsonUtil; -import org.session.libsession.utilities.Util; - import org.thoughtcrime.securesms.mms.MediaStream; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.PartAuthority; - import org.thoughtcrime.securesms.util.BitmapDecodingException; import org.thoughtcrime.securesms.util.BitmapUtil; -import org.session.libsignal.utilities.externalstorage.ExternalStorageUtil; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData; import org.thoughtcrime.securesms.video.EncryptedMediaDataSource; @@ -240,7 +234,11 @@ public class AttachmentDatabase extends Database { null, null, null); while (cursor != null && cursor.moveToNext()) { - results.addAll(getAttachment(cursor)); + List attachments = getAttachment(cursor); + for (DatabaseAttachment attachment : attachments) { + if (attachment.isQuote()) continue; + results.add(attachment); + } } return results; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index d4861570f1..ffa4c3642a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -18,19 +18,19 @@ package org.thoughtcrime.securesms.database; import android.content.Context; import android.database.Cursor; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteQueryBuilder; +import org.session.libsession.messaging.threads.Address; +import org.session.libsession.utilities.Util; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.session.libsession.messaging.threads.Address; -import org.session.libsession.utilities.Util; - import java.util.HashSet; import java.util.Set; @@ -79,18 +79,16 @@ public class MmsSmsDatabase extends Database { } public @Nullable MessageRecord getMessageForTimestamp(long timestamp) { - MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context); try (Cursor cursor = queryTables(PROJECTION, MmsSmsColumns.NORMALIZED_DATE_SENT + " = " + timestamp, null, null)) { - MmsSmsDatabase.Reader reader = db.readerFor(cursor); + MmsSmsDatabase.Reader reader = readerFor(cursor); return reader.getNext(); } } public @Nullable MessageRecord getMessageFor(long timestamp, String serializedAuthor) { - MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context); try (Cursor cursor = queryTables(PROJECTION, MmsSmsColumns.NORMALIZED_DATE_SENT + " = " + timestamp, null, null)) { - MmsSmsDatabase.Reader reader = db.readerFor(cursor); + MmsSmsDatabase.Reader reader = readerFor(cursor); MessageRecord messageRecord; diff --git a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt index d15274dfcb..66fcb15f7b 100644 --- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -32,8 +32,8 @@ interface MessageDataProvider { // Quotes fun getMessageForQuote(timestamp: Long, author: Address): Long? - fun getAttachmentsAndLinkPreviewFor(messageID: Long): List - fun getMessageBodyFor(messageID: Long): String + fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List + fun getMessageBodyFor(timestamp: Long, author: String): String fun getAttachmentIDsFor(messageID: Long): List fun getLinkPreviewAttachmentIDFor(messageID: Long): Long? diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java index b65698e01a..64b8dcf2b2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java @@ -72,7 +72,7 @@ public class IncomingMediaMessage { Optional quote, Optional> linkPreviews) { - return new IncomingMediaMessage(from, message.getReceivedTimestamp(), -1, expiresIn, false, + return new IncomingMediaMessage(from, message.getSentTimestamp(), -1, expiresIn, false, false, Optional.fromNullable(message.getText()), group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews); } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index 79b063e98f..a00521a4da 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -154,8 +154,8 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS val author = Address.fromSerialized(quote.author) val messageID = MessagingConfiguration.shared.messageDataProvider.getMessageForQuote(quote.id, author) if (messageID != null) { - val attachmentsWithLinkPreview = MessagingConfiguration.shared.messageDataProvider.getAttachmentsAndLinkPreviewFor(messageID) - quoteModel = QuoteModel(quote.id, author, MessagingConfiguration.shared.messageDataProvider.getMessageBodyFor(messageID), false, attachmentsWithLinkPreview) + val attachments = MessagingConfiguration.shared.messageDataProvider.getAttachmentsAndLinkPreviewFor(messageID) + quoteModel = QuoteModel(quote.id, author, MessagingConfiguration.shared.messageDataProvider.getMessageBodyFor(quote.id, quote.author), false, attachments) } else { quoteModel = QuoteModel(quote.id, author, quote.text, true, PointerAttachment.forPointers(proto.dataMessage.quote.attachmentsList)) } From 53c966b634eda93eacf0021c82ed7d03fc88581f Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 25 Mar 2021 13:25:07 +1100 Subject: [PATCH 27/51] fix: race in receive messages processing from restore --- .../session/libsession/messaging/jobs/MessageReceiveJob.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt index 9dac6de010..143394312f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt @@ -18,6 +18,8 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val val TAG = MessageReceiveJob::class.simpleName val KEY: String = "MessageReceiveJob" + private val RECEIVE_LOCK = Object() + //keys used for database storage purpose private val KEY_DATA = "data" private val KEY_IS_BACKGROUND_POLL = "is_background_poll" @@ -34,7 +36,9 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val try { val isRetry: Boolean = failureCount != 0 val (message, proto) = MessageReceiver.parse(this.data, this.openGroupMessageServerID, isRetry) - MessageReceiver.handle(message, proto, this.openGroupID) + synchronized(RECEIVE_LOCK) { + MessageReceiver.handle(message, proto, this.openGroupID) + } this.handleSuccess() deferred.resolve(Unit) } catch (e: Exception) { From bdc086cacfd1ded4c9624d142424dceb25128cdc Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 25 Mar 2021 14:11:58 +1100 Subject: [PATCH 28/51] fix: use message's expiry instead of the recipient's --- .../main/java/org/thoughtcrime/securesms/database/Storage.kt | 4 ++-- .../libsession/messaging/messages/visible/VisibleMessage.kt | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 610bde1bed..5f021a3f9c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -131,7 +131,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val signalServiceAttachments = attachments.mapNotNull { it.toSignalPointer() } - val mediaMessage = IncomingMediaMessage.from(message, senderAddress, senderRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews) + val mediaMessage = IncomingMediaMessage.from(message, senderAddress, message.expiry * 1000L, group, signalServiceAttachments, quote, linkPreviews) mmsDatabase.beginTransaction() mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp ?: 0) } @@ -156,7 +156,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val textMessage = OutgoingTextMessage.from(message, Recipient.from(context, targetAddress, false)) smsDatabase.insertMessageOutbox(message.threadID ?: -1, textMessage, message.sentTimestamp!!) } else { - val textMessage = IncomingTextMessage.from(message, senderAddress, group, senderRecipient.expireMessages * 1000L) + val textMessage = IncomingTextMessage.from(message, senderAddress, group, message.expiry * 1000L) val encrypted = IncomingEncryptedMessage(textMessage, textMessage.messageBody) smsDatabase.insertMessageInbox(encrypted, message.sentTimestamp ?: 0) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt index 0b2c4b59e8..7863896e3f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt @@ -20,6 +20,7 @@ class VisibleMessage : Message() { var linkPreview: LinkPreview? = null var contact: Contact? = null var profile: Profile? = null + var expiry: Int = 0 override val isSelfSendValid: Boolean = true @@ -47,6 +48,9 @@ class VisibleMessage : Message() { // TODO Contact val profile = Profile.fromProto(dataMessage) profile?.let { result.profile = profile } + if (dataMessage.hasExpireTimer()) { + result.expiry = dataMessage.expireTimer + } return result } } From ef3bef787d5c23a1bc4be95e24ec276c17090718 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Thu, 25 Mar 2021 14:55:23 +1100 Subject: [PATCH 29/51] expiration timer handling updated --- .../service/ExpiringMessageManager.java | 45 ++++++++++++------- .../MessageReceiverHandler.kt | 21 ++------- .../libsession/utilities/SSKEnvironment.kt | 5 ++- 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java index 77eee247cd..bd5b11cab2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -4,8 +4,10 @@ import android.content.Context; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate; import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.recipients.Recipient; +import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.SSKEnvironment; import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.service.api.messages.SignalServiceGroup; @@ -20,6 +22,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord; import org.session.libsession.messaging.messages.signal.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.MmsException; +import java.io.IOException; import java.util.Comparator; import java.util.TreeSet; import java.util.concurrent.Executor; @@ -65,20 +68,30 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM } @Override - public void setExpirationTimer(@Nullable Long messageID, int duration, @NotNull String senderPublicKey, @NotNull SignalServiceProtos.Content content) { + public void setExpirationTimer(@NotNull ExpirationTimerUpdate message) { + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + + String senderPublicKey = message.getSender(); + int duration = message.getDuration(); + String groupPK = message.getGroupPublicKey(); + Long sentTimestamp = message.getSentTimestamp(); + + Optional groupInfo = Optional.absent(); + Address address; + try { - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - Address address = Address.fromSerialized(senderPublicKey); + if (groupPK != null) { + String groupID = GroupUtil.doubleEncodeGroupID(groupPK); + groupInfo = Optional.of(new SignalServiceGroup(GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL)); + address = Address.fromSerialized(groupID); + } else { + address = Address.fromSerialized(senderPublicKey); + } Recipient recipient = Recipient.from(context, address, false); if (recipient.isBlocked()) return; - Optional groupInfo = Optional.absent(); - if (content.getDataMessage().hasGroup()) { - GroupContext groupContext = content.getDataMessage().getGroup(); - groupInfo = Optional.of(new SignalServiceGroup(groupContext.getId().toByteArray(), SignalServiceGroup.GroupType.SIGNAL)); - } - IncomingMediaMessage mediaMessage = new IncomingMediaMessage(address, content.getDataMessage().getTimestamp(), -1, + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(address, sentTimestamp, -1, duration * 1000L, true, false, Optional.absent(), @@ -87,22 +100,24 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM Optional.absent(), Optional.absent(), Optional.absent()); - + //insert the timer update message database.insertSecureDecryptedMessageInbox(mediaMessage, -1); - + //set the timer to the conversation DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, duration); - if (messageID != null) { - DatabaseFactory.getSmsDatabase(context).deleteMessage(messageID); + if (message.getId() != null) { + DatabaseFactory.getSmsDatabase(context).deleteMessage(message.getId()); } } catch (MmsException e) { Log.e("Loki", "Failed to insert expiration update message."); + } catch (IOException ioe) { + Log.e("Loki", "Failed to insert expiration update message."); } } @Override - public void disableExpirationTimer(@Nullable Long messageID, @NotNull String senderPublicKey, @NotNull SignalServiceProtos.Content content) { - setExpirationTimer(messageID, 0, senderPublicKey, content); + public void disableExpirationTimer(@NotNull ExpirationTimerUpdate message) { + setExpirationTimer(message); } @Override diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index a00521a4da..643df0ae44 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -41,7 +41,7 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, is ReadReceipt -> handleReadReceipt(message) is TypingIndicator -> handleTypingIndicator(message) is ClosedGroupControlMessage -> handleClosedGroupControlMessage(message) - is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message, proto) + is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message) is ConfigurationMessage -> handleConfigurationMessage(message) is VisibleMessage -> handleVisibleMessage(message, proto, openGroupID) } @@ -80,27 +80,14 @@ fun MessageReceiver.cancelTypingIndicatorsIfNeeded(senderPublicKey: String) { SSKEnvironment.shared.typingIndicators.didReceiveIncomingMessage(context, threadID, address, 1) } -private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimerUpdate, proto: SignalServiceProtos.Content) { +private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimerUpdate) { if (message.duration!! > 0) { - setExpirationTimer(message, proto) + SSKEnvironment.shared.messageExpirationManager.setExpirationTimer(message) } else { - disableExpirationTimer(message, proto) + SSKEnvironment.shared.messageExpirationManager.disableExpirationTimer(message) } } -fun MessageReceiver.setExpirationTimer(message: ExpirationTimerUpdate, proto: SignalServiceProtos.Content) { - val id = message.id - val duration = message.duration!! - val senderPublicKey = message.sender!! - SSKEnvironment.shared.messageExpirationManager.setExpirationTimer(id, duration, senderPublicKey, proto) -} - -fun MessageReceiver.disableExpirationTimer(message: ExpirationTimerUpdate, proto: SignalServiceProtos.Content) { - val id = message.id - val senderPublicKey = message.sender!! - SSKEnvironment.shared.messageExpirationManager.disableExpirationTimer(id, senderPublicKey, proto) -} - private fun MessageReceiver.handleConfigurationMessage(message: ConfigurationMessage) { val context = MessagingConfiguration.shared.context val storage = MessagingConfiguration.shared.storage diff --git a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt index 3c4612c412..081300bc96 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt @@ -1,6 +1,7 @@ package org.session.libsession.utilities import android.content.Context +import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.recipients.Recipient @@ -36,8 +37,8 @@ class SSKEnvironment( } interface MessageExpirationManagerProtocol { - fun setExpirationTimer(messageID: Long?, duration: Int, senderPublicKey: String, content: SignalServiceProtos.Content) - fun disableExpirationTimer(messageID: Long?, senderPublicKey: String, content: SignalServiceProtos.Content) + fun setExpirationTimer(message: ExpirationTimerUpdate) + fun disableExpirationTimer(message: ExpirationTimerUpdate) fun startAnyExpiration(timestamp: Long, author: String) } From 196a0205ae3c9806315715a808ed4cf91f0629fb Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 25 Mar 2021 15:12:55 +1100 Subject: [PATCH 30/51] refactor: revert expiry to be recipient based instead of message based --- .../securesms/database/Storage.kt | 39 +++++++------------ .../messages/visible/VisibleMessage.kt | 4 -- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 5f021a3f9c..367223411f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -30,7 +30,6 @@ import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos -import org.session.libsignal.utilities.logging.Log import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol @@ -95,7 +94,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, override fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? { var messageID: Long? = null val senderAddress = Address.fromSerialized(message.sender!!) - val senderRecipient = Recipient.from(context, senderAddress, false) val group: Optional = when { openGroupID != null -> Optional.of(SignalServiceGroup(openGroupID.toByteArray(), SignalServiceGroup.GroupType.PUBLIC_CHAT)) groupPublicKey != null -> { @@ -107,23 +105,22 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val pointerAttachments = attachments.mapNotNull { it.toSignalAttachment() } + val targetAddress = if (senderAddress.serialize() == getUserPublicKey() && message.syncTarget != null) { + Address.fromSerialized(message.syncTarget!!) + } else if (group.isPresent) { + Address.fromSerialized(GroupUtil.getEncodedId(group.get())) + } else { + senderAddress + } + val targetRecipient = Recipient.from(context, targetAddress, false) + if (message.isMediaMessage() || attachments.isNotEmpty()) { 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 mmsDatabase = DatabaseFactory.getMmsDatabase(context) val insertResult = if (message.sender == getUserPublicKey()) { - val targetAddress = if (message.syncTarget != null) { - Address.fromSerialized(message.syncTarget!!) - } else { - if (group.isPresent) { - Address.fromSerialized(GroupUtil.getEncodedId(group.get())) - } else { - Log.d("Loki", "Cannot handle message from self.") - return null - } - } - val mediaMessage = OutgoingMediaMessage.from(message, Recipient.from(context, targetAddress, false), pointerAttachments, quote.orNull(), linkPreviews.orNull().firstOrNull()) + val mediaMessage = OutgoingMediaMessage.from(message, targetRecipient, pointerAttachments, quote.orNull(), linkPreviews.orNull().firstOrNull()) mmsDatabase.beginTransaction() mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) } else { @@ -131,7 +128,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val signalServiceAttachments = attachments.mapNotNull { it.toSignalPointer() } - val mediaMessage = IncomingMediaMessage.from(message, senderAddress, message.expiry * 1000L, group, signalServiceAttachments, quote, linkPreviews) + val mediaMessage = IncomingMediaMessage.from(message, senderAddress, targetRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews) mmsDatabase.beginTransaction() mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp ?: 0) } @@ -143,20 +140,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } else { val smsDatabase = DatabaseFactory.getSmsDatabase(context) val insertResult = if (message.sender == getUserPublicKey()) { - val targetAddress = if (message.syncTarget != null) { - Address.fromSerialized(message.syncTarget!!) - } else { - if (group.isPresent) { - Address.fromSerialized(GroupUtil.getEncodedId(group.get())) - } else { - Log.d("Loki", "Cannot handle message from self.") - return null - } - } - val textMessage = OutgoingTextMessage.from(message, Recipient.from(context, targetAddress, false)) + val textMessage = OutgoingTextMessage.from(message, targetRecipient) smsDatabase.insertMessageOutbox(message.threadID ?: -1, textMessage, message.sentTimestamp!!) } else { - val textMessage = IncomingTextMessage.from(message, senderAddress, group, message.expiry * 1000L) + val textMessage = IncomingTextMessage.from(message, senderAddress, group, targetRecipient.expireMessages * 1000L) val encrypted = IncomingEncryptedMessage(textMessage, textMessage.messageBody) smsDatabase.insertMessageInbox(encrypted, message.sentTimestamp ?: 0) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt index 7863896e3f..0b2c4b59e8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt @@ -20,7 +20,6 @@ class VisibleMessage : Message() { var linkPreview: LinkPreview? = null var contact: Contact? = null var profile: Profile? = null - var expiry: Int = 0 override val isSelfSendValid: Boolean = true @@ -48,9 +47,6 @@ class VisibleMessage : Message() { // TODO Contact val profile = Profile.fromProto(dataMessage) profile?.let { result.profile = profile } - if (dataMessage.hasExpireTimer()) { - result.expiry = dataMessage.expireTimer - } return result } } From e3e45292a858e88568da4f76ff0ea8f5e5a18193 Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 25 Mar 2021 16:40:15 +1100 Subject: [PATCH 31/51] fix: unclosed cursor error message --- .../securesms/MediaPreviewActivity.java | 34 +++++++++++-------- .../mediapreview/MediaPreviewViewModel.java | 13 ++++--- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index 6399dbc022..60b5cbeb25 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -18,9 +18,6 @@ package org.thoughtcrime.securesms; import android.annotation.SuppressLint; import android.annotation.TargetApi; - -import androidx.appcompat.app.ActionBar; -import androidx.lifecycle.ViewModelProvider; import android.content.Context; import android.content.Intent; import android.database.Cursor; @@ -30,16 +27,6 @@ import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.Loader; -import androidx.core.util.Pair; -import androidx.viewpager.widget.PagerAdapter; -import androidx.viewpager.widget.ViewPager; -import androidx.appcompat.app.AlertDialog; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import android.view.GestureDetector; import android.view.LayoutInflater; import android.view.Menu; @@ -53,16 +40,28 @@ import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.core.util.Pair; +import androidx.lifecycle.ViewModelProvider; +import androidx.loader.app.LoaderManager; +import androidx.loader.content.Loader; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener; import org.session.libsession.utilities.Util; - +import org.session.libsignal.utilities.logging.Log; import org.thoughtcrime.securesms.components.MediaView; import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord; import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader; -import org.session.libsignal.utilities.logging.Log; import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel; import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter; import org.thoughtcrime.securesms.mms.GlideApp; @@ -312,6 +311,11 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im private int cleanupMedia() { int restartItem = mediaPager.getCurrentItem(); + PagerAdapter adapter = mediaPager.getAdapter(); + if (adapter instanceof CursorPagerAdapter) { + ((CursorPagerAdapter)adapter).cursor.close(); + } + mediaPager.removeAllViews(); mediaPager.setAdapter(null); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewViewModel.java index 261d9856d8..0babefc0e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewViewModel.java @@ -1,17 +1,18 @@ package org.thoughtcrime.securesms.mediapreview; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; import android.content.Context; import android.database.Cursor; import android.net.Uri; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; +import org.session.libsignal.libsignal.util.guava.Optional; import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord; import org.thoughtcrime.securesms.mediasend.Media; -import org.session.libsignal.libsignal.util.guava.Optional; import java.util.Collections; import java.util.LinkedList; @@ -27,7 +28,9 @@ public class MediaPreviewViewModel extends ViewModel { public void setCursor(@NonNull Context context, @Nullable Cursor cursor, boolean leftIsRecent) { boolean firstLoad = (this.cursor == null) && (cursor != null); - + if (this.cursor != null) { + this.cursor.close(); + } this.cursor = cursor; this.leftIsRecent = leftIsRecent; From d7c03c9d0a57b450db7bd71fe5a325c0f9b138f5 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Fri, 26 Mar 2021 15:46:37 +1100 Subject: [PATCH 32/51] group updates fixes --- .../conversation/ConversationActivity.java | 2 +- .../activities/EditClosedGroupActivity.kt | 2 +- .../securesms/loki/activities/HomeActivity.kt | 2 +- .../MessageReceiverHandler.kt | 39 +++++++++++++++---- .../sending_receiving/MessageSender.kt | 6 +-- 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index ca14153ed3..6301ec6b56 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -1011,7 +1011,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } try { if (isClosedGroup) { - MessageSender.explicitLeave(groupPublicKey); + MessageSender.explicitLeave(groupPublicKey, true); initializeEnabledCheck(); } else { Toast.makeText(this, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt index 2a319c514f..e969819d07 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt @@ -277,7 +277,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { isLoading = true loaderContainer.fadeIn() val promise: Promise = if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) { - MessageSender.explicitLeave(groupPublicKey!!) + MessageSender.explicitLeave(groupPublicKey!!, true) } else { task { if (hasNameChanged) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 281d42b552..64116414bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -343,7 +343,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), isClosedGroup = false } if (isClosedGroup) { - MessageSender.explicitLeave(groupPublicKey!!) + MessageSender.explicitLeave(groupPublicKey!!, false) } else { Toast.makeText(context, R.string.activity_home_leaving_group_failed_message, Toast.LENGTH_LONG).show() return@launch diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index 643df0ae44..647cb094d2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -307,12 +307,12 @@ private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGr Log.d("Loki", "Ignoring closed group info message for inactive group") return } - if (!group.members.map { it.toString() }.contains(senderPublicKey)) { + if (!group.admins.map { it.toString() }.contains(senderPublicKey)) { Log.d("Loki", "Ignoring closed group encryption key pair from non-member.") return } // Find our wrapper and decrypt it if possible - val wrapper = kind.wrappers.firstOrNull { it.publicKey!!.toByteArray().toHexString() == userPublicKey } ?: return + val wrapper = kind.wrappers.firstOrNull { it.publicKey!! == userPublicKey } ?: return val encryptedKeyPair = wrapper.encryptedKeyPair!!.toByteArray() val plaintext = MessageReceiverDecryption.decryptWithSessionProtocol(encryptedKeyPair, userKeyPair).first // Parse it @@ -331,6 +331,7 @@ private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGr private fun MessageReceiver.handleClosedGroupNameChanged(message: ClosedGroupControlMessage) { val context = MessagingConfiguration.shared.context val storage = MessagingConfiguration.shared.storage + val userPublicKey = TextSecurePreferences.getLocalNumber(context) val senderPublicKey = message.sender ?: return val kind = message.kind!! as? ClosedGroupControlMessage.Kind.NameChange ?: return val groupPublicKey = message.groupPublicKey ?: return @@ -353,7 +354,14 @@ private fun MessageReceiver.handleClosedGroupNameChanged(message: ClosedGroupCon val name = kind.name storage.updateTitle(groupID, name) - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, message.sentTimestamp!!) + // Notify the user + if (userPublicKey == senderPublicKey) { + // sender is a linked device + val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) + storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, name, members, admins, threadID, message.sentTimestamp!!) + } else { + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, message.sentTimestamp!!) + } } private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupControlMessage) { @@ -381,7 +389,9 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo val updateMembers = kind.members.map { it.toByteArray().toHexString() } val newMembers = members + updateMembers storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) }) + // Notify the user if (userPublicKey == senderPublicKey) { + // sender is a linked device val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, name, members, admins, threadID, message.sentTimestamp!!) } else { @@ -399,7 +409,6 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo } } } - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, message.sentTimestamp!!) } private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroupControlMessage) { @@ -452,7 +461,14 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup if (senderLeft) SignalServiceProtos.GroupContext.Type.QUIT to SignalServiceGroup.Type.QUIT else SignalServiceProtos.GroupContext.Type.UPDATE to SignalServiceGroup.Type.UPDATE - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins, message.sentTimestamp!!) + // Notify the user + if (userPublicKey == senderPublicKey) { + // sender is a linked device + val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) + storage.insertOutgoingInfoMessage(context, groupID, contextType, name, members, admins, threadID, message.sentTimestamp!!) + } else { + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, contextType, signalType, name, members, admins, message.sentTimestamp!!) + } } private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupControlMessage) { @@ -481,8 +497,10 @@ private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupCont // If admin leaves the group is disbanded val didAdminLeave = admins.contains(senderPublicKey) val updatedMemberList = members - senderPublicKey + val userLeft = (userPublicKey == senderPublicKey) - if (didAdminLeave) { + if (didAdminLeave || userLeft) { + // admin left the group of linked device left the group disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey) } else { val isCurrentUserAdmin = admins.contains(userPublicKey) @@ -491,7 +509,14 @@ private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupCont MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMemberList) } } - storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.QUIT, SignalServiceGroup.Type.QUIT, name, members, admins, message.sentTimestamp!!) + // Notify the user + if (userLeft) { + //sender is a linked device + val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) + storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.QUIT, name, members, admins, threadID, message.sentTimestamp!!) + } else { + storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.QUIT, SignalServiceGroup.Type.QUIT, name, members, admins, message.sentTimestamp!!) + } } private fun MessageReceiver.handleClosedGroupEncryptionKeyPairRequest(message: ClosedGroupControlMessage) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index cd78bcecbb..225c38b7da 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -178,8 +178,8 @@ object MessageSender { if (shouldNotify) { val notifyPNServerJob = NotifyPNServerJob(snodeMessage) JobQueue.shared.add(notifyPNServerJob) - deferred.resolve(Unit) } + deferred.resolve(Unit) } promise.fail { errorCount += 1 @@ -336,7 +336,7 @@ object MessageSender { } @JvmStatic - fun explicitLeave(groupPublicKey: String): Promise { - return leave(groupPublicKey, false) + fun explicitLeave(groupPublicKey: String, notifyUser: Boolean): Promise { + return leave(groupPublicKey, notifyUser) } } \ No newline at end of file From 8bc6df5d1640fa10849733dcc48095ad07672d7c Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 29 Mar 2021 13:52:36 +1100 Subject: [PATCH 33/51] fix multi device attachment issue --- .../securesms/database/Storage.kt | 2 +- .../messaging/messages/visible/Attachment.kt | 9 +++----- .../attachments/PointerAttachment.java | 21 +++++++++++++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 367223411f..d6358d165f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -120,7 +120,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val mmsDatabase = DatabaseFactory.getMmsDatabase(context) val insertResult = if (message.sender == getUserPublicKey()) { - val mediaMessage = OutgoingMediaMessage.from(message, targetRecipient, pointerAttachments, quote.orNull(), linkPreviews.orNull().firstOrNull()) + val mediaMessage = OutgoingMediaMessage.from(message, targetRecipient, pointerAttachments, quote.orNull(), linkPreviews.orNull()?.firstOrNull()) mmsDatabase.beginTransaction() mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) } else { diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Attachment.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Attachment.kt index 24226c7274..c7c6a670e6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Attachment.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Attachment.kt @@ -3,13 +3,12 @@ package org.session.libsession.messaging.messages.visible import android.util.Size import android.webkit.MimeTypeMap import com.google.protobuf.ByteString -import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment +import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment +import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.internal.push.SignalServiceProtos -import org.session.libsignal.utilities.Base64 import java.io.File -import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment class Attachment { @@ -101,9 +100,7 @@ class Attachment { fun toSignalAttachment(): SignalAttachment? { if (!isValid()) return null - return DatabaseAttachment(null, 0, false, false, contentType, 0, - sizeInBytes?.toLong() ?: 0, if (fileName.isNullOrEmpty()) null else fileName, null, Base64.encodeBytes(key), null, digest, null, kind == Kind.VOICE_MESSAGE, - size?.width ?: 0, size?.height ?: 0, false, caption, url) + return PointerAttachment.forAttachment((this)) } fun toSignalPointer(): SignalServiceAttachmentPointer? { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/PointerAttachment.java b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/PointerAttachment.java index 91f0f166b3..e1826d3ecc 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/PointerAttachment.java +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/PointerAttachment.java @@ -169,4 +169,25 @@ public class PointerAttachment extends Attachment { thumbnail != null ? thumbnail.asPointer().getCaption().orNull() : null, thumbnail != null ? thumbnail.asPointer().getUrl() : "")); } + + /** + * Converts a Session Attachment to a Signal Attachment + * @param attachment Session Attachment + * @return Signal Attachment + */ + public static Attachment forAttachment(org.session.libsession.messaging.messages.visible.Attachment attachment) { + return new PointerAttachment(attachment.getContentType(), + AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING, + attachment.getSizeInBytes(), + attachment.getFileName(), + null, Base64.encodeBytes(attachment.getKey()), + null, + attachment.getDigest(), + null, + attachment.getKind() == org.session.libsession.messaging.messages.visible.Attachment.Kind.VOICE_MESSAGE, + attachment.getSize() != null ? attachment.getSize().getWidth() : 0, + attachment.getSize() != null ? attachment.getSize().getHeight() : 0, + attachment.getCaption(), + attachment.getUrl()); + } } From db553544ecf776e5b74b795a0420d7fc344ca7d3 Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 29 Mar 2021 14:16:49 +1100 Subject: [PATCH 34/51] fix: open group fixes for new message receive pipeline --- .../securesms/ApplicationContext.java | 4 +- .../securesms/database/Storage.kt | 9 +- .../securesms/loki/api/PublicChatManager.kt | 7 +- .../loki/protocol/MultiDeviceProtocol.kt | 1 - .../messaging/jobs/AttachmentDownloadJob.kt | 2 +- .../sending_receiving/MessageReceiver.kt | 4 +- .../pollers/OpenGroupPoller.kt | 301 +++++++++--------- 7 files changed, 158 insertions(+), 170 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index b39dd3c5b0..8de298955c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -191,16 +191,16 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc // Set application UI mode (day/night theme) to the user selected one. UiModeUtilities.setupUiModeToUserSelected(this); // ======== - initializeJobManager(); initializeExpiringMessageManager(); initializeTypingStatusRepository(); initializeTypingStatusSender(); initializeReadReceiptManager(); initializeProfileManager(); initializePeriodicTasks(); + SSKEnvironment.Companion.configure(getTypingStatusRepository(), getReadReceiptManager(), getProfileManager(), messageNotifier, getExpiringMessageManager()); + initializeJobManager(); initializeWebRtc(); initializeBlobProvider(); - SSKEnvironment.Companion.configure(getTypingStatusRepository(), getReadReceiptManager(), getProfileManager(), messageNotifier, getExpiringMessageManager()); } @Override 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 367223411f..177006deb1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -94,6 +94,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, override fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? { var messageID: Long? = null val senderAddress = Address.fromSerialized(message.sender!!) + val isUserSender = message.sender!! == getUserPublicKey() val group: Optional = when { openGroupID != null -> Optional.of(SignalServiceGroup(openGroupID.toByteArray(), SignalServiceGroup.GroupType.PUBLIC_CHAT)) groupPublicKey != null -> { @@ -105,7 +106,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val pointerAttachments = attachments.mapNotNull { it.toSignalAttachment() } - val targetAddress = if (senderAddress.serialize() == getUserPublicKey() && message.syncTarget != null) { + val targetAddress = if (isUserSender && !message.syncTarget.isNullOrEmpty()) { Address.fromSerialized(message.syncTarget!!) } else if (group.isPresent) { Address.fromSerialized(GroupUtil.getEncodedId(group.get())) @@ -130,7 +131,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } val mediaMessage = IncomingMediaMessage.from(message, senderAddress, targetRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews) mmsDatabase.beginTransaction() - mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp ?: 0) + mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.receivedTimestamp ?: 0) } if (insertResult.isPresent) { mmsDatabase.setTransactionSuccessful() @@ -145,7 +146,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } else { val textMessage = IncomingTextMessage.from(message, senderAddress, group, targetRecipient.expireMessages * 1000L) val encrypted = IncomingEncryptedMessage(textMessage, textMessage.messageBody) - smsDatabase.insertMessageInbox(encrypted, message.sentTimestamp ?: 0) + smsDatabase.insertMessageInbox(encrypted, message.receivedTimestamp ?: 0) } insertResult.orNull()?.let { result -> messageID = result.messageId @@ -474,7 +475,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, override fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): Long { val database = DatabaseFactory.getThreadDatabase(context) if (!openGroupID.isNullOrEmpty()) { - val recipient = Recipient.from(context, Address.fromSerialized(openGroupID), false) + val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())), false) return database.getOrCreateThreadIdFor(recipient) } else if (!groupPublicKey.isNullOrEmpty()) { val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)), false) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt index aca0b020f7..c069cdfe26 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt @@ -17,12 +17,14 @@ import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.util.BitmapUtil +import java.util.concurrent.Executors class PublicChatManager(private val context: Context) { private var chats = mutableMapOf() private val pollers = mutableMapOf() private val observers = mutableMapOf() private var isPolling = false + private val executorService = Executors.newScheduledThreadPool(16) public fun areAllCaughtUp(): Boolean { var areAllCaughtUp = true @@ -37,7 +39,7 @@ class PublicChatManager(private val context: Context) { public fun markAllAsNotCaughtUp() { refreshChatsAndPollers() for ((threadID, chat) in chats) { - val poller = pollers[threadID] ?: OpenGroupPoller(chat) + val poller = pollers[threadID] ?: OpenGroupPoller(chat, executorService) poller.isCaughtUp = false } } @@ -46,7 +48,7 @@ class PublicChatManager(private val context: Context) { refreshChatsAndPollers() for ((threadId, chat) in chats) { - val poller = pollers[threadId] ?: OpenGroupPoller(chat) + val poller = pollers[threadId] ?: OpenGroupPoller(chat, executorService) poller.startIfNeeded() listenToThreadDeletion(threadId) if (!pollers.containsKey(threadId)) { pollers[threadId] = poller } @@ -57,6 +59,7 @@ class PublicChatManager(private val context: Context) { public fun stopPollers() { pollers.values.forEach { it.stop() } isPolling = false + executorService.shutdown() } //TODO Declare a specific type of checked exception instead of "Exception". diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt index 69dfc70126..41b3de72f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt @@ -126,6 +126,5 @@ object MultiDeviceProtocol { threadDatabase.notifyUpdatedFromConfig() } } - // TODO: handle new configuration message fields or handle in new pipeline } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index f500ea4841..9f99aff6bf 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -62,7 +62,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) // DECRYPTION // Assume we're retrieving an attachment for an open group server if the digest is not set - val stream = if (attachment.digest == null || attachment.key == null) FileInputStream(tempFile) + val stream = if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) FileInputStream(tempFile) else AttachmentCipherInputStream.createForAttachment(tempFile, attachment.size, Base64.decode(attachment.key), attachment.digest) messageDataProvider.insertAttachment(databaseMessageID, attachment.attachmentId, stream) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index 89cc060ec1..e1b7e652ef 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -7,6 +7,7 @@ import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.utilities.GroupUtil import org.session.libsignal.service.internal.push.PushTransportDetails import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.session.libsignal.utilities.logging.Log object MessageReceiver { @@ -121,7 +122,8 @@ object MessageReceiver { message.sender = sender message.recipient = userPublicKey message.sentTimestamp = envelope.timestamp - message.receivedTimestamp = System.currentTimeMillis() + message.receivedTimestamp = if (envelope.hasServerTimestamp()) envelope.serverTimestamp else System.currentTimeMillis() + Log.d("Loki", "time: ${envelope.timestamp}, sent: ${envelope.serverTimestamp}") message.groupPublicKey = groupPublicKey message.openGroupServerMessageID = openGroupServerID // Validate diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index 608a225089..a6d233b482 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -1,8 +1,6 @@ package org.session.libsession.messaging.sending_receiving.pollers -import android.os.Handler import com.google.protobuf.ByteString - import nl.komponents.kovenant.Promise import nl.komponents.kovenant.deferred import org.session.libsession.messaging.MessagingConfiguration @@ -11,61 +9,37 @@ import org.session.libsession.messaging.jobs.MessageReceiveJob import org.session.libsession.messaging.opengroups.OpenGroup import org.session.libsession.messaging.opengroups.OpenGroupAPI import org.session.libsession.messaging.opengroups.OpenGroupMessage - -import org.session.libsignal.utilities.successBackground -import org.session.libsignal.utilities.logging.Log import org.session.libsignal.service.internal.push.SignalServiceProtos.* - +import org.session.libsignal.utilities.logging.Log +import org.session.libsignal.utilities.successBackground import java.util.* +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit + +class OpenGroupPoller(private val openGroup: OpenGroup, private val executorService: ScheduledExecutorService? = null) { -class OpenGroupPoller(private val openGroup: OpenGroup) { - private val handler by lazy { Handler() } private var hasStarted = false - private var isPollOngoing = false - public var isCaughtUp = false + @Volatile private var isPollOngoing = false + var isCaughtUp = false + + private val cancellableFutures = mutableListOf>() // region Convenience private val userHexEncodedPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey() ?: "" - private var displayNameUpdatees = setOf() + private var displayNameUpdates = setOf() // endregion // region Tasks - private val pollForNewMessagesTask = object : Runnable { - - override fun run() { - pollForNewMessages() - handler.postDelayed(this, pollForNewMessagesInterval) - } - } - - private val pollForDeletedMessagesTask = object : Runnable { - - override fun run() { - pollForDeletedMessages() - handler.postDelayed(this, pollForDeletedMessagesInterval) - } - } - - private val pollForModeratorsTask = object : Runnable { - - override fun run() { - pollForModerators() - handler.postDelayed(this, pollForModeratorsInterval) - } - } - - private val pollForDisplayNamesTask = object : Runnable { - - override fun run() { - pollForDisplayNames() - handler.postDelayed(this, pollForDisplayNamesInterval) - } - } + private val pollForNewMessagesTask = Runnable { pollForNewMessages() } + private val pollForDeletedMessagesTask = Runnable { pollForDeletedMessages() } + private val pollForModeratorsTask = Runnable { pollForModerators() } + private val pollForDisplayNamesTask = Runnable { pollForDisplayNames() } // endregion // region Settings companion object { - private val pollForNewMessagesInterval: Long = 4 * 1000 + private val pollForNewMessagesInterval: Long = 10 * 1000 private val pollForDeletedMessagesInterval: Long = 60 * 1000 private val pollForModeratorsInterval: Long = 10 * 60 * 1000 private val pollForDisplayNamesInterval: Long = 60 * 1000 @@ -74,19 +48,21 @@ class OpenGroupPoller(private val openGroup: OpenGroup) { // region Lifecycle fun startIfNeeded() { - if (hasStarted) return - pollForNewMessagesTask.run() - pollForDeletedMessagesTask.run() - pollForModeratorsTask.run() - pollForDisplayNamesTask.run() + if (hasStarted || executorService == null) return + cancellableFutures += listOf( + executorService.scheduleAtFixedRate(pollForNewMessagesTask,0, pollForNewMessagesInterval, TimeUnit.MILLISECONDS), + executorService.scheduleAtFixedRate(pollForDeletedMessagesTask,0, pollForDeletedMessagesInterval, TimeUnit.MILLISECONDS), + executorService.scheduleAtFixedRate(pollForModeratorsTask,0, pollForModeratorsInterval, TimeUnit.MILLISECONDS), + executorService.scheduleAtFixedRate(pollForDisplayNamesTask,0, pollForDisplayNamesInterval, TimeUnit.MILLISECONDS) + ) hasStarted = true } fun stop() { - handler.removeCallbacks(pollForNewMessagesTask) - handler.removeCallbacks(pollForDeletedMessagesTask) - handler.removeCallbacks(pollForModeratorsTask) - handler.removeCallbacks(pollForDisplayNamesTask) + cancellableFutures.forEach { future -> + future.cancel(false) + } + cancellableFutures.clear() hasStarted = false } // endregion @@ -96,120 +72,127 @@ class OpenGroupPoller(private val openGroup: OpenGroup) { return pollForNewMessages(false) } - fun pollForNewMessages(isBackgroundPoll: Boolean): Promise { + private fun pollForNewMessages(isBackgroundPoll: Boolean): Promise { if (isPollOngoing) { return Promise.of(Unit) } isPollOngoing = true val deferred = deferred() // Kovenant propagates a context to chained promises, so OpenGroupAPI.sharedContext should be used for all of the below OpenGroupAPI.getMessages(openGroup.channel, openGroup.server).successBackground { messages -> // Process messages in the background + Log.d("Loki", "received ${messages.size} messages") messages.forEach { message -> - val senderPublicKey = message.senderPublicKey - val wasSentByCurrentUser = (senderPublicKey == userHexEncodedPublicKey) - fun generateDisplayName(rawDisplayName: String): String { - return "${rawDisplayName} (${senderPublicKey.takeLast(8)})" - } - val senderDisplayName = MessagingConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.channel, openGroup.server) ?: generateDisplayName("Anonymous") - val id = openGroup.id.toByteArray() - // Main message - val dataMessageProto = DataMessage.newBuilder() - val body = if (message.body == message.timestamp.toString()) { "" } else { message.body } - dataMessageProto.setBody(body) - dataMessageProto.setTimestamp(message.timestamp) - // Attachments - val attachmentProtos = message.attachments.mapNotNull { attachment -> - if (attachment.kind != OpenGroupMessage.Attachment.Kind.Attachment) { return@mapNotNull null } - val attachmentProto = AttachmentPointer.newBuilder() - attachmentProto.setId(attachment.serverID) - attachmentProto.setContentType(attachment.contentType) - attachmentProto.setSize(attachment.size) - attachmentProto.setFileName(attachment.fileName) - attachmentProto.setFlags(attachment.flags) - attachmentProto.setWidth(attachment.width) - attachmentProto.setHeight(attachment.height) - attachment.caption.let { attachmentProto.setCaption(it) } - attachmentProto.setUrl(attachment.url) - attachmentProto.build() - } - dataMessageProto.addAllAttachments(attachmentProtos) - // Link preview - val linkPreview = message.attachments.firstOrNull { it.kind == OpenGroupMessage.Attachment.Kind.LinkPreview } - if (linkPreview != null) { - val linkPreviewProto = DataMessage.Preview.newBuilder() - linkPreviewProto.setUrl(linkPreview.linkPreviewURL!!) - linkPreviewProto.setTitle(linkPreview.linkPreviewTitle!!) - val attachmentProto = AttachmentPointer.newBuilder() - attachmentProto.setId(linkPreview.serverID) - attachmentProto.setContentType(linkPreview.contentType) - attachmentProto.setSize(linkPreview.size) - attachmentProto.setFileName(linkPreview.fileName) - attachmentProto.setFlags(linkPreview.flags) - attachmentProto.setWidth(linkPreview.width) - attachmentProto.setHeight(linkPreview.height) - linkPreview.caption.let { attachmentProto.setCaption(it) } - attachmentProto.setUrl(linkPreview.url) - linkPreviewProto.setImage(attachmentProto.build()) - dataMessageProto.addPreview(linkPreviewProto.build()) - } - // Quote - val quote = message.quote - if (quote != null) { - val quoteProto = DataMessage.Quote.newBuilder() - quoteProto.setId(quote.quotedMessageTimestamp) - quoteProto.setAuthor(quote.quoteePublicKey) - if (quote.quotedMessageBody != quote.quotedMessageTimestamp.toString()) { quoteProto.setText(quote.quotedMessageBody) } - dataMessageProto.setQuote(quoteProto.build()) - } - val messageServerID = message.serverID - // Profile - val profileProto = DataMessage.LokiProfile.newBuilder() - profileProto.setDisplayName(message.displayName) - val profilePicture = message.profilePicture - if (profilePicture != null) { - profileProto.setProfilePicture(profilePicture.url) - dataMessageProto.setProfileKey(ByteString.copyFrom(profilePicture.profileKey)) - } - dataMessageProto.setProfile(profileProto.build()) - /* TODO: the signal service proto needs to be synced with iOS - // Open group info - if (messageServerID != null) { - val openGroupProto = PublicChatInfo.newBuilder() - openGroupProto.setServerID(messageServerID) - dataMessageProto.setPublicChatInfo(openGroupProto.build()) - } - */ - // Signal group context - val groupProto = GroupContext.newBuilder() - groupProto.setId(ByteString.copyFrom(id)) - groupProto.setType(GroupContext.Type.DELIVER) - groupProto.setName(openGroup.displayName) - dataMessageProto.setGroup(groupProto.build()) - // Sync target - if (wasSentByCurrentUser) { - dataMessageProto.setSyncTarget(openGroup.id) - } - // Content - val content = Content.newBuilder() - content.setDataMessage(dataMessageProto.build()) - // Envelope - val builder = Envelope.newBuilder() - builder.type = Envelope.Type.UNIDENTIFIED_SENDER - builder.source = senderPublicKey - builder.sourceDevice = 1 - builder.setContent(content.build().toByteString()) - builder.serverTimestamp = message.serverTimestamp - val envelope = builder.build() - val job = MessageReceiveJob(envelope.toByteArray(), isBackgroundPoll, messageServerID, openGroup.id) - if (isBackgroundPoll) { - job.executeAsync().success { deferred.resolve(Unit) }.fail { deferred.resolve(Unit) } - // The promise is just used to keep track of when we're done - } else { - JobQueue.shared.add(job) - deferred.resolve(Unit) + try { + val senderPublicKey = message.senderPublicKey + fun generateDisplayName(rawDisplayName: String): String { + return "${rawDisplayName} (${senderPublicKey.takeLast(8)})" + } + val senderDisplayName = MessagingConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.channel, openGroup.server) ?: generateDisplayName("Anonymous") + val id = openGroup.id.toByteArray() + // Main message + val dataMessageProto = DataMessage.newBuilder() + val body = if (message.body == message.timestamp.toString()) { "" } else { message.body } + dataMessageProto.setBody(body) + dataMessageProto.setTimestamp(message.timestamp) + // Attachments + val attachmentProtos = message.attachments.mapNotNull { attachment -> + try { + if (attachment.kind != OpenGroupMessage.Attachment.Kind.Attachment) { return@mapNotNull null } + val attachmentProto = AttachmentPointer.newBuilder() + attachmentProto.setId(attachment.serverID) + attachmentProto.setContentType(attachment.contentType) + attachmentProto.setSize(attachment.size) + attachmentProto.setFileName(attachment.fileName) + attachmentProto.setFlags(attachment.flags) + attachmentProto.setWidth(attachment.width) + attachmentProto.setHeight(attachment.height) + attachment.caption?.let { attachmentProto.setCaption(it) } + attachmentProto.setUrl(attachment.url) + attachmentProto.build() + } catch (e: Exception) { + Log.e("Loki","Failed to parse attachment as proto",e) + null + } + } + dataMessageProto.addAllAttachments(attachmentProtos) + // Link preview + val linkPreview = message.attachments.firstOrNull { it.kind == OpenGroupMessage.Attachment.Kind.LinkPreview } + if (linkPreview != null) { + val linkPreviewProto = DataMessage.Preview.newBuilder() + linkPreviewProto.setUrl(linkPreview.linkPreviewURL!!) + linkPreviewProto.setTitle(linkPreview.linkPreviewTitle!!) + val attachmentProto = AttachmentPointer.newBuilder() + attachmentProto.setId(linkPreview.serverID) + attachmentProto.setContentType(linkPreview.contentType) + attachmentProto.setSize(linkPreview.size) + attachmentProto.setFileName(linkPreview.fileName) + attachmentProto.setFlags(linkPreview.flags) + attachmentProto.setWidth(linkPreview.width) + attachmentProto.setHeight(linkPreview.height) + linkPreview.caption?.let { attachmentProto.setCaption(it) } + attachmentProto.setUrl(linkPreview.url) + linkPreviewProto.setImage(attachmentProto.build()) + dataMessageProto.addPreview(linkPreviewProto.build()) + } + // Quote + val quote = message.quote + if (quote != null) { + val quoteProto = DataMessage.Quote.newBuilder() + quoteProto.setId(quote.quotedMessageTimestamp) + quoteProto.setAuthor(quote.quoteePublicKey) + if (quote.quotedMessageBody != quote.quotedMessageTimestamp.toString()) { quoteProto.setText(quote.quotedMessageBody) } + dataMessageProto.setQuote(quoteProto.build()) + } + val messageServerID = message.serverID + // Profile + val profileProto = DataMessage.LokiProfile.newBuilder() + profileProto.setDisplayName(message.displayName) + val profilePicture = message.profilePicture + if (profilePicture != null) { + profileProto.setProfilePicture(profilePicture.url) + dataMessageProto.setProfileKey(ByteString.copyFrom(profilePicture.profileKey)) + } + dataMessageProto.setProfile(profileProto.build()) + /* TODO: the signal service proto needs to be synced with iOS + // Open group info + if (messageServerID != null) { + val openGroupProto = PublicChatInfo.newBuilder() + openGroupProto.setServerID(messageServerID) + dataMessageProto.setPublicChatInfo(openGroupProto.build()) + } + */ + // Signal group context + val groupProto = GroupContext.newBuilder() + groupProto.setId(ByteString.copyFrom(id)) + groupProto.setType(GroupContext.Type.DELIVER) + groupProto.setName(openGroup.displayName) + dataMessageProto.setGroup(groupProto.build()) + // Content + val content = Content.newBuilder() + content.setDataMessage(dataMessageProto.build()) + // Envelope + val builder = Envelope.newBuilder() + builder.type = Envelope.Type.UNIDENTIFIED_SENDER + builder.source = senderPublicKey + builder.sourceDevice = 1 + builder.setContent(content.build().toByteString()) + builder.timestamp = message.timestamp + builder.serverTimestamp = message.serverTimestamp + val envelope = builder.build() + val job = MessageReceiveJob(envelope.toByteArray(), isBackgroundPoll, messageServerID, openGroup.id) + Log.d("Loki", "Scheduling Job $job") + if (isBackgroundPoll) { + job.executeAsync().always { deferred.resolve(Unit) } + // The promise is just used to keep track of when we're done + } else { + JobQueue.shared.add(job) + } + } catch (e: Exception) { + Log.e("Loki", "Exception parsing message", e) } } isCaughtUp = true isPollOngoing = false + deferred.resolve(Unit) }.fail { Log.d("Loki", "Failed to get messages for group chat with ID: ${openGroup.channel} on server: ${openGroup.server}.") isPollOngoing = false @@ -218,16 +201,16 @@ class OpenGroupPoller(private val openGroup: OpenGroup) { } private fun pollForDisplayNames() { - if (displayNameUpdatees.isEmpty()) { return } - val hexEncodedPublicKeys = displayNameUpdatees - displayNameUpdatees = setOf() + if (displayNameUpdates.isEmpty()) { return } + val hexEncodedPublicKeys = displayNameUpdates + displayNameUpdates = setOf() OpenGroupAPI.getDisplayNames(hexEncodedPublicKeys, openGroup.server).successBackground { mapping -> for (pair in mapping.entries) { val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})" MessagingConfiguration.shared.storage.setOpenGroupDisplayName(pair.key, openGroup.channel, openGroup.server, senderDisplayName) } }.fail { - displayNameUpdatees = displayNameUpdatees.union(hexEncodedPublicKeys) + displayNameUpdates = displayNameUpdates.union(hexEncodedPublicKeys) } } From c3f7425ccd35c972bd8dd24d404fed2fc700f39f Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 29 Mar 2021 16:40:56 +1100 Subject: [PATCH 35/51] fix: empty messages in open groups now correct properly --- .../securesms/database/Storage.kt | 3 +- .../messaging/jobs/AttachmentDownloadJob.kt | 61 ++++++++++--------- .../messaging/opengroups/OpenGroupMessage.kt | 3 +- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index d7643d1cfc..69c4f2e076 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -182,8 +182,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, override fun resumeMessageSendJobIfNeeded(messageSendJobID: String) { val job = DatabaseFactory.getSessionJobDatabase(context).getMessageSendJob(messageSendJobID) ?: return - job.delegate = JobQueue.shared - job.execute() + JobQueue.shared.add(job) } override fun isJobCanceled(job: Job): Boolean { diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index 9f99aff6bf..46dcbae37b 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -34,41 +34,44 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) } override fun execute() { - val messageDataProvider = MessagingConfiguration.shared.messageDataProvider - messageDataProvider.getDatabaseAttachment(attachmentID) - val attachment = messageDataProvider.getDatabaseAttachment(attachmentID) ?: return handleFailure(Error.NoAttachment) - messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID) - val tempFile = createTempFile() - val handleFailure: (java.lang.Exception) -> Unit = { exception -> - tempFile.delete() - if(exception is Error && exception == Error.NoAttachment) { - MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) - this.handlePermanentFailure(exception) - } else if (exception is DotNetAPI.Error && exception == DotNetAPI.Error.ParsingFailed) { - // No need to retry if the response is invalid. Most likely this means we (incorrectly) - // got a "Cannot GET ..." error from the file server. - MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) - this.handlePermanentFailure(exception) - } else { - this.handleFailure(exception) - } - } try { - FileServerAPI.shared.downloadFile(tempFile, attachment.url, MAX_ATTACHMENT_SIZE, null) - } catch (e: Exception) { - return handleFailure(e) - } + val messageDataProvider = MessagingConfiguration.shared.messageDataProvider + val attachment = messageDataProvider.getDatabaseAttachment(attachmentID) ?: return handleFailure(Error.NoAttachment) + messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID) + val tempFile = createTempFile() + val handleFailure: (java.lang.Exception) -> Unit = { exception -> + tempFile.delete() + if(exception is Error && exception == Error.NoAttachment) { + MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) + this.handlePermanentFailure(exception) + } else if (exception is DotNetAPI.Error && exception == DotNetAPI.Error.ParsingFailed) { + // No need to retry if the response is invalid. Most likely this means we (incorrectly) + // got a "Cannot GET ..." error from the file server. + MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) + this.handlePermanentFailure(exception) + } else { + this.handleFailure(exception) + } + } + try { + FileServerAPI.shared.downloadFile(tempFile, attachment.url, MAX_ATTACHMENT_SIZE, null) + } catch (e: Exception) { + return handleFailure(e) + } - // DECRYPTION + // DECRYPTION - // Assume we're retrieving an attachment for an open group server if the digest is not set - val stream = if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) FileInputStream(tempFile) + // Assume we're retrieving an attachment for an open group server if the digest is not set + val stream = if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) FileInputStream(tempFile) else AttachmentCipherInputStream.createForAttachment(tempFile, attachment.size, Base64.decode(attachment.key), attachment.digest) - messageDataProvider.insertAttachment(databaseMessageID, attachment.attachmentId, stream) + messageDataProvider.insertAttachment(databaseMessageID, attachment.attachmentId, stream) - tempFile.delete() - handleSuccess() + tempFile.delete() + handleSuccess() + } catch (e: Exception) { + handleFailure(e) + } } private fun handleSuccess() { diff --git a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessage.kt index 0a414649d8..5a05d0f5f8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupMessage.kt @@ -42,7 +42,8 @@ data class OpenGroupMessage( }() // Message val displayname = storage.getUserDisplayName() ?: "Anonymous" - val body = message.text ?: message.sentTimestamp.toString() // The back-end doesn't accept messages without a body so we use this as a workaround + val text = message.text + val body = if (text.isNullOrEmpty()) message.sentTimestamp.toString() else text // The back-end doesn't accept messages without a body so we use this as a workaround val result = OpenGroupMessage(null, userPublicKey, displayname, body, message.sentTimestamp!!, OpenGroupAPI.openGroupMessageType, quote, mutableListOf(), null, null, 0) // Link preview val linkPreview = message.linkPreview From 0292e702462e75a9997bdbc76e21ece5215f797c Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 30 Mar 2021 13:09:40 +1100 Subject: [PATCH 36/51] fix quote attachment issue --- .../securesms/attachments/DatabaseAttachmentProvider.kt | 4 ++-- .../org/session/libsession/database/MessageDataProvider.kt | 2 +- .../messaging/sending_receiving/MessageReceiverHandler.kt | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt index 105cabf37b..963b6c1299 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -66,10 +66,10 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) attachmentDatabase.setTransferState(messageID, AttachmentId(attachmentId, 0), attachmentState.value) } - override fun getMessageForQuote(timestamp: Long, author: Address): Long? { + override fun getMessageForQuote(timestamp: Long, author: Address): Pair? { val messagingDatabase = DatabaseFactory.getMmsSmsDatabase(context) val message = messagingDatabase.getMessageFor(timestamp, author) - return message?.id + return if (message != null) Pair(message.id, message.isMms) else null } override fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List { diff --git a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt index 66fcb15f7b..d3065e5772 100644 --- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -31,7 +31,7 @@ interface MessageDataProvider { fun updateAttachmentAfterUploadFailed(attachmentId: Long) // Quotes - fun getMessageForQuote(timestamp: Long, author: Address): Long? + fun getMessageForQuote(timestamp: Long, author: Address): Pair? fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List fun getMessageBodyFor(timestamp: Long, author: String): String diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index 647cb094d2..8245116dca 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -29,6 +29,7 @@ import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.utilities.logging.Log import java.security.MessageDigest import java.util.* +import kotlin.collections.ArrayList internal fun MessageReceiver.isBlock(publicKey: String): Boolean { val context = MessagingConfiguration.shared.context @@ -139,9 +140,9 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS if (message.quote != null && proto.dataMessage.hasQuote()) { val quote = proto.dataMessage.quote val author = Address.fromSerialized(quote.author) - val messageID = MessagingConfiguration.shared.messageDataProvider.getMessageForQuote(quote.id, author) - if (messageID != null) { - val attachments = MessagingConfiguration.shared.messageDataProvider.getAttachmentsAndLinkPreviewFor(messageID) + val messageInfo = MessagingConfiguration.shared.messageDataProvider.getMessageForQuote(quote.id, author) + if (messageInfo != null) { + val attachments = if (messageInfo.second) MessagingConfiguration.shared.messageDataProvider.getAttachmentsAndLinkPreviewFor(messageInfo.first) else ArrayList() quoteModel = QuoteModel(quote.id, author, MessagingConfiguration.shared.messageDataProvider.getMessageBodyFor(quote.id, quote.author), false, attachments) } else { quoteModel = QuoteModel(quote.id, author, quote.text, true, PointerAttachment.forPointers(proto.dataMessage.quote.attachmentsList)) From 3654d1731cd87febfe00715379372ba55ec5d389 Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 30 Mar 2021 16:23:12 +1100 Subject: [PATCH 37/51] fix: various fixes wrt open groups, config messages, job queueing --- .../securesms/ApplicationContext.java | 6 +-- .../securesms/database/Storage.kt | 47 +++++++++++++++++++ .../securesms/jobs/PushReceivedJob.java | 6 +-- .../loki/api/PushNotificationService.kt | 13 +++-- .../libsession/messaging/StorageProtocol.kt | 5 ++ .../messaging/jobs/AttachmentDownloadJob.kt | 36 +++++++------- .../libsession/messaging/jobs/JobQueue.kt | 12 ++--- .../MessageReceiverHandler.kt | 27 +++++++++-- .../pollers/OpenGroupPoller.kt | 7 +-- 9 files changed, 109 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 8de298955c..c07dca4f7d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -230,14 +230,14 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc if (closedGroupPoller != null) { closedGroupPoller.stopIfNeeded(); } - if (publicChatManager != null) { - publicChatManager.stopPollers(); - } } @Override public void onTerminate() { stopKovenant(); // Loki + if (publicChatManager != null) { + publicChatManager.stopPollers(); + } super.onTerminate(); } 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 69c4f2e076..eaf72e6dbb 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 org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageSendJob +import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.signal.* import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.visible.Attachment @@ -30,7 +31,9 @@ import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper +import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities @@ -65,12 +68,27 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return TextSecurePreferences.getProfilePictureURL(context) } + override fun setUserProfilePictureUrl(newProfilePicture: String) { + val ourRecipient = Address.fromSerialized(getUserPublicKey()!!).let { + Recipient.from(context, it, false) + } + TextSecurePreferences.setProfilePictureURL(context, newProfilePicture) + RetrieveProfileAvatarJob(ourRecipient, newProfilePicture) + ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, newProfilePicture)) + } + override fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray? { val address = Address.fromSerialized(recipientPublicKey) val recipient = Recipient.from(context, address, false) return recipient.profileKey } + override fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) { + val address = Address.fromSerialized(recipientPublicKey) + val recipient = Recipient.from(context, address, false) + DatabaseFactory.getRecipientDatabase(context).setProfileKey(recipient, profileKey) + } + override fun getOrGenerateRegistrationID(): Int { var registrationID = TextSecurePreferences.getLocalRegistrationId(context) if (registrationID == 0) { @@ -511,6 +529,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) } + override fun setDisplayName(publicKey: String, newName: String) { + DatabaseFactory.getLokiUserDatabase(context).setDisplayName(publicKey, newName) + } + override fun getServerDisplayName(serverID: String, publicKey: String): String? { return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(serverID, publicKey) } @@ -524,6 +546,31 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return if (recipientSettings.isPresent) { recipientSettings.get() } else null } + override fun addContacts(contacts: List) { + val recipientDatabase = DatabaseFactory.getRecipientDatabase(context) + val threadDatabase = DatabaseFactory.getThreadDatabase(context) + for (contact in contacts) { + val address = Address.fromSerialized(contact.publicKey) + val recipient = Recipient.from(context, address, true) + if (!contact.profilePicture.isNullOrEmpty()) { + recipientDatabase.setProfileAvatar(recipient, contact.profilePicture) + } + if (contact.profileKey?.isNotEmpty() == true) { + recipientDatabase.setProfileKey(recipient, contact.profileKey) + } + if (contact.name.isNotEmpty()) { + recipientDatabase.setProfileName(recipient, contact.name) + } + recipientDatabase.setProfileSharing(recipient, true) + recipientDatabase.setRegistered(recipient, Recipient.RegisteredState.REGISTERED) + // create Thread if needed + threadDatabase.getOrCreateThreadIdFor(recipient) + } + if (contacts.isNotEmpty()) { + threadDatabase.notifyUpdatedFromConfig() + } + } + override fun getAttachmentDataUri(attachmentId: AttachmentId): Uri { return PartAuthority.getAttachmentDataUri(attachmentId) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushReceivedJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushReceivedJob.java index ac2519228d..70f9b755b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushReceivedJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushReceivedJob.java @@ -3,11 +3,11 @@ package org.thoughtcrime.securesms.jobs; import androidx.annotation.NonNull; import org.session.libsession.messaging.threads.Address; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.session.libsignal.utilities.logging.Log; import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsignal.service.api.messages.SignalServiceEnvelope; +import org.session.libsignal.utilities.logging.Log; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.jobmanager.Job; public abstract class PushReceivedJob extends BaseJob { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PushNotificationService.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PushNotificationService.kt index 27f67c9a69..0f5244ae19 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PushNotificationService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PushNotificationService.kt @@ -4,13 +4,13 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage -import org.thoughtcrime.securesms.jobs.PushContentReceiveJob -import org.thoughtcrime.securesms.notifications.NotificationChannels +import org.session.libsession.messaging.jobs.JobQueue +import org.session.libsession.messaging.jobs.MessageReceiveJob import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.logging.Log -import org.session.libsignal.service.api.messages.SignalServiceEnvelope -import org.session.libsignal.utilities.Base64 import org.session.libsignal.service.loki.api.MessageWrapper +import org.session.libsignal.utilities.Base64 +import org.session.libsignal.utilities.logging.Log +import org.thoughtcrime.securesms.notifications.NotificationChannels class PushNotificationService : FirebaseMessagingService() { @@ -27,8 +27,7 @@ class PushNotificationService : FirebaseMessagingService() { val data = base64EncodedData?.let { Base64.decode(it) } if (data != null) { try { - val envelope = MessageWrapper.unwrap(data) - PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope), true) + JobQueue.shared.add(MessageReceiveJob(MessageWrapper.unwrap(data).toByteArray(),true)) } catch (e: Exception) { Log.d("Loki", "Failed to unwrap data for message due to error: $e.") } diff --git a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt index 8a392953d2..64c1828d51 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -6,6 +6,7 @@ import android.net.Uri import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.MessageSendJob +import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.opengroups.OpenGroup @@ -30,8 +31,10 @@ interface StorageProtocol { fun getUserDisplayName(): String? fun getUserProfileKey(): ByteArray? fun getUserProfilePictureURL(): String? + fun setUserProfilePictureUrl(newProfilePicture: String) fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray? + fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) // Signal Protocol @@ -140,11 +143,13 @@ interface StorageProtocol { // Loki User fun getDisplayName(publicKey: String): String? + fun setDisplayName(publicKey: String, newName: String) fun getServerDisplayName(serverID: String, publicKey: String): String? fun getProfilePictureURL(publicKey: String): String? // Recipient fun getRecipientSettings(address: Address): RecipientSettings? + fun addContacts(contacts: List) // PartAuthority fun getAttachmentDataUri(attachmentId: AttachmentId): Uri diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index 46dcbae37b..1a0b9f9ef3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -34,30 +34,26 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) } override fun execute() { + val handleFailure: (java.lang.Exception) -> Unit = { exception -> + if(exception is Error && exception == Error.NoAttachment) { + MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) + this.handlePermanentFailure(exception) + } else if (exception is DotNetAPI.Error && exception == DotNetAPI.Error.ParsingFailed) { + // No need to retry if the response is invalid. Most likely this means we (incorrectly) + // got a "Cannot GET ..." error from the file server. + MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) + this.handlePermanentFailure(exception) + } else { + this.handleFailure(exception) + } + } try { val messageDataProvider = MessagingConfiguration.shared.messageDataProvider val attachment = messageDataProvider.getDatabaseAttachment(attachmentID) ?: return handleFailure(Error.NoAttachment) messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID) val tempFile = createTempFile() - val handleFailure: (java.lang.Exception) -> Unit = { exception -> - tempFile.delete() - if(exception is Error && exception == Error.NoAttachment) { - MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) - this.handlePermanentFailure(exception) - } else if (exception is DotNetAPI.Error && exception == DotNetAPI.Error.ParsingFailed) { - // No need to retry if the response is invalid. Most likely this means we (incorrectly) - // got a "Cannot GET ..." error from the file server. - MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) - this.handlePermanentFailure(exception) - } else { - this.handleFailure(exception) - } - } - try { - FileServerAPI.shared.downloadFile(tempFile, attachment.url, MAX_ATTACHMENT_SIZE, null) - } catch (e: Exception) { - return handleFailure(e) - } + + FileServerAPI.shared.downloadFile(tempFile, attachment.url, MAX_ATTACHMENT_SIZE, null) // DECRYPTION @@ -70,7 +66,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) tempFile.delete() handleSuccess() } catch (e: Exception) { - handleFailure(e) + return handleFailure(e) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index 96b00c8397..2934e8dcd7 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -16,19 +16,17 @@ import kotlin.math.roundToLong class JobQueue : JobDelegate { private var hasResumedPendingJobs = false // Just for debugging - private val dispatcher = Executors.newCachedThreadPool().asCoroutineDispatcher() + private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val scope = GlobalScope + SupervisorJob() private val queue = Channel(UNLIMITED) init { // process jobs - scope.launch { + scope.launch(dispatcher) { while (isActive) { queue.receive().let { job -> - launch(dispatcher) { - job.delegate = this@JobQueue - job.execute() - } + job.delegate = this@JobQueue + job.execute() } } } @@ -44,7 +42,7 @@ class JobQueue : JobDelegate { queue.offer(job) // offer always called on unlimited capacity } - fun addWithoutExecuting(job: Job) { + private fun addWithoutExecuting(job: Job) { job.id = System.currentTimeMillis().toString() MessagingConfiguration.shared.storage.persistJob(job) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index 647cb094d2..686d8fba1a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -18,6 +18,7 @@ import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.preferences.ProfileKeyUtil import org.session.libsignal.libsignal.ecc.DjbECPrivateKey import org.session.libsignal.libsignal.ecc.DjbECPublicKey import org.session.libsignal.libsignal.ecc.ECKeyPair @@ -26,6 +27,7 @@ import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.toHexString +import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.logging.Log import java.security.MessageDigest import java.util.* @@ -91,8 +93,11 @@ private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimer private fun MessageReceiver.handleConfigurationMessage(message: ConfigurationMessage) { val context = MessagingConfiguration.shared.context val storage = MessagingConfiguration.shared.storage - if (TextSecurePreferences.getConfigurationMessageSynced(context)) return - if (message.sender != storage.getUserPublicKey()) return + if (TextSecurePreferences.getConfigurationMessageSynced(context) && !TextSecurePreferences.shouldUpdateProfile(context, message.sentTimestamp!!)) return + val userPublicKey = storage.getUserPublicKey() + if (userPublicKey == null || message.sender != storage.getUserPublicKey()) return + TextSecurePreferences.setConfigurationMessageSynced(context, true) + TextSecurePreferences.setLastProfileUpdateTime(context, message.sentTimestamp!!) val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys() for (closeGroup in message.closedGroups) { if (allClosedGroupPublicKeys.contains(closeGroup.publicKey)) continue @@ -103,8 +108,20 @@ private fun MessageReceiver.handleConfigurationMessage(message: ConfigurationMes if (allOpenGroups.contains(openGroup)) continue storage.addOpenGroup(openGroup, 1) } - // TODO: in future handle the latest in config messages - TextSecurePreferences.setConfigurationMessageSynced(context, true) + if (message.displayName.isNotEmpty()) { + TextSecurePreferences.setProfileName(context, message.displayName) + storage.setDisplayName(userPublicKey, message.displayName) + } + if (message.profileKey.isNotEmpty()) { + val profileKey = Base64.encodeBytes(message.profileKey) + ProfileKeyUtil.setEncodedProfileKey(context, profileKey) + storage.setProfileKeyForRecipient(userPublicKey, message.profileKey) + // handle profile photo + if (!message.profilePicture.isNullOrEmpty() && TextSecurePreferences.getProfilePictureURL(context) != message.profilePicture) { + storage.setUserProfilePictureUrl(message.profilePicture!!) + } + } + storage.addContacts(message.contacts) } fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalServiceProtos.Content, openGroupID: String?) { @@ -112,7 +129,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS val context = MessagingConfiguration.shared.context // Update profile if needed val newProfile = message.profile - if (newProfile != null) { + if (newProfile != null && openGroupID.isNullOrEmpty()) { val profileManager = SSKEnvironment.shared.profileManager val recipient = Recipient.from(context, Address.fromSerialized(message.sender!!), false) val displayName = newProfile.displayName!! diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index a6d233b482..5bbae93ca6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -83,10 +83,7 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ messages.forEach { message -> try { val senderPublicKey = message.senderPublicKey - fun generateDisplayName(rawDisplayName: String): String { - return "${rawDisplayName} (${senderPublicKey.takeLast(8)})" - } - val senderDisplayName = MessagingConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.channel, openGroup.server) ?: generateDisplayName("Anonymous") + val senderDisplayName = message.displayName val id = openGroup.id.toByteArray() // Main message val dataMessageProto = DataMessage.newBuilder() @@ -145,7 +142,7 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ val messageServerID = message.serverID // Profile val profileProto = DataMessage.LokiProfile.newBuilder() - profileProto.setDisplayName(message.displayName) + profileProto.setDisplayName(senderDisplayName) val profilePicture = message.profilePicture if (profilePicture != null) { profileProto.setProfilePicture(profilePicture.url) From a445e0a3264cb5cc1d1ab3e0b7afb6a452e47b91 Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 30 Mar 2021 17:13:25 +1100 Subject: [PATCH 38/51] fix: moderator status going off open chat API instead of PublicChatAPI --- .../conversation/ConversationItem.java | 29 +-- .../securesms/loki/api/PublicChatPoller.kt | 238 ++++++++++++++++++ .../MessageReceiverHandler.kt | 2 +- .../pollers/OpenGroupPoller.kt | 6 +- 4 files changed, 257 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index 650a708e53..44f93c8332 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -52,11 +52,21 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; - +import org.session.libsession.messaging.opengroups.OpenGroupAPI; import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress; +import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; +import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; +import org.session.libsession.messaging.threads.recipients.Recipient; +import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener; +import org.session.libsession.utilities.GroupUtil; +import org.session.libsession.utilities.TextSecurePreferences; +import org.session.libsession.utilities.ThemeUtil; +import org.session.libsession.utilities.Util; +import org.session.libsession.utilities.ViewUtil; +import org.session.libsession.utilities.views.Stub; import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.service.loki.api.opengroups.PublicChat; -import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI; +import org.session.libsignal.utilities.logging.Log; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.BindableConversationItem; import org.thoughtcrime.securesms.MediaPreviewActivity; @@ -69,7 +79,6 @@ import org.thoughtcrime.securesms.components.LinkPreviewView; import org.thoughtcrime.securesms.components.QuoteView; import org.thoughtcrime.securesms.components.StickerView; import org.thoughtcrime.securesms.components.emoji.EmojiTextView; -import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; @@ -78,7 +87,6 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.Quote; import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob; import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; -import org.session.libsignal.utilities.logging.Log; import org.thoughtcrime.securesms.loki.utilities.MentionUtilities; import org.thoughtcrime.securesms.loki.views.MessageAudioView; import org.thoughtcrime.securesms.loki.views.ProfilePictureView; @@ -89,22 +97,11 @@ import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideClickListener; import org.thoughtcrime.securesms.mms.SlidesClickedListener; import org.thoughtcrime.securesms.mms.TextSlide; -import org.session.libsession.messaging.threads.recipients.Recipient; -import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.LongClickCopySpan; import org.thoughtcrime.securesms.util.LongClickMovementMethod; import org.thoughtcrime.securesms.util.SearchUtil; -import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; -import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.ThemeUtil; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.GroupUtil; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.views.Stub; - import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -916,7 +913,7 @@ public class ConversationItem extends LinearLayout PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(messageRecord.getThreadId()); if (publicChat != null) { - boolean isModerator = PublicChatAPI.Companion.isUserModerator(current.getRecipient().getAddress().toString(), publicChat.getChannel(), publicChat.getServer()); + boolean isModerator = OpenGroupAPI.isUserModerator(current.getRecipient().getAddress().toString(), publicChat.getChannel(), publicChat.getServer()); visibility = isModerator ? View.VISIBLE : View.GONE; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt new file mode 100644 index 0000000000..87c0e29570 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt @@ -0,0 +1,238 @@ +package org.thoughtcrime.securesms.loki.api + +import android.content.Context +import android.os.Handler +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.functional.bind +import nl.komponents.kovenant.functional.map +import org.session.libsession.messaging.threads.Address +import org.session.libsession.messaging.threads.recipients.Recipient +import org.session.libsession.utilities.IdentityKeyUtil +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.libsignal.util.guava.Optional +import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer +import org.session.libsignal.service.api.messages.SignalServiceContent +import org.session.libsignal.service.api.messages.SignalServiceDataMessage +import org.session.libsignal.service.api.messages.SignalServiceGroup +import org.session.libsignal.service.api.push.SignalServiceAddress +import org.session.libsignal.service.loki.api.fileserver.FileServerAPI +import org.session.libsignal.service.loki.api.opengroups.PublicChat +import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI +import org.session.libsignal.service.loki.api.opengroups.PublicChatMessage +import org.session.libsignal.utilities.logging.Log +import org.session.libsignal.utilities.successBackground +import org.thoughtcrime.securesms.ApplicationContext +import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.jobs.PushDecryptJob +import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob +import java.security.MessageDigest +import java.util.* + +class PublicChatPoller(private val context: Context, private val group: PublicChat) { + private val handler by lazy { Handler() } + private var hasStarted = false + private var isPollOngoing = false + public var isCaughtUp = false + + // region Convenience + private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)!! + private var displayNameUpdatees = setOf() + + private val api: PublicChatAPI + get() = { + val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() + val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context) + val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context) + val openGroupDatabase = DatabaseFactory.getGroupDatabase(context) + PublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase, openGroupDatabase) + }() + // endregion + + // region Tasks + private val pollForNewMessagesTask = object : Runnable { + + override fun run() { + pollForNewMessages() + handler.postDelayed(this, pollForNewMessagesInterval) + } + } + + private val pollForDeletedMessagesTask = object : Runnable { + + override fun run() { + pollForDeletedMessages() + handler.postDelayed(this, pollForDeletedMessagesInterval) + } + } + + private val pollForModeratorsTask = object : Runnable { + + override fun run() { + pollForModerators() + handler.postDelayed(this, pollForModeratorsInterval) + } + } + + private val pollForDisplayNamesTask = object : Runnable { + + override fun run() { + pollForDisplayNames() + handler.postDelayed(this, pollForDisplayNamesInterval) + } + } + // endregion + + // region Settings + companion object { + private val pollForNewMessagesInterval: Long = 4 * 1000 + private val pollForDeletedMessagesInterval: Long = 60 * 1000 + private val pollForModeratorsInterval: Long = 10 * 60 * 1000 + private val pollForDisplayNamesInterval: Long = 60 * 1000 + } + // endregion + + // region Lifecycle + fun startIfNeeded() { + if (hasStarted) return + pollForNewMessagesTask.run() + pollForDeletedMessagesTask.run() + pollForModeratorsTask.run() + pollForDisplayNamesTask.run() + hasStarted = true + } + + fun stop() { + handler.removeCallbacks(pollForNewMessagesTask) + handler.removeCallbacks(pollForDeletedMessagesTask) + handler.removeCallbacks(pollForModeratorsTask) + handler.removeCallbacks(pollForDisplayNamesTask) + hasStarted = false + } + // endregion + + // region Polling + private fun getDataMessage(message: PublicChatMessage): SignalServiceDataMessage { + val id = group.id.toByteArray() + val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.PUBLIC_CHAT, null, null, null, null) + val quote = if (message.quote != null) { + SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteePublicKey), message.quote!!.quotedMessageBody, listOf()) + } else { + null + } + val attachments = message.attachments.mapNotNull { attachment -> + if (attachment.kind != PublicChatMessage.Attachment.Kind.Attachment) { return@mapNotNull null } + SignalServiceAttachmentPointer( + attachment.serverID, + attachment.contentType, + ByteArray(0), + Optional.of(attachment.size), + Optional.absent(), + attachment.width, attachment.height, + Optional.absent(), + Optional.of(attachment.fileName), + false, + Optional.fromNullable(attachment.caption), + attachment.url) + } + val linkPreview = message.attachments.firstOrNull { it.kind == PublicChatMessage.Attachment.Kind.LinkPreview } + val signalLinkPreviews = mutableListOf() + if (linkPreview != null) { + val attachment = SignalServiceAttachmentPointer( + linkPreview.serverID, + linkPreview.contentType, + ByteArray(0), + Optional.of(linkPreview.size), + Optional.absent(), + linkPreview.width, linkPreview.height, + Optional.absent(), + Optional.of(linkPreview.fileName), + false, + Optional.fromNullable(linkPreview.caption), + linkPreview.url) + signalLinkPreviews.add(SignalServiceDataMessage.Preview(linkPreview.linkPreviewURL!!, linkPreview.linkPreviewTitle!!, Optional.of(attachment))) + } + val body = if (message.body == message.timestamp.toString()) "" else message.body // Workaround for the fact that the back-end doesn't accept messages without a body + val syncTarget = if (message.senderPublicKey == userHexEncodedPublicKey) group.id else null + return SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, 0, false, null, quote, null, signalLinkPreviews, null, syncTarget) + } + + fun pollForNewMessages(): Promise { + if (isPollOngoing) { return Promise.of(Unit) } + isPollOngoing = true + val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() + val apiDB = DatabaseFactory.getLokiAPIDatabase(context) + FileServerAPI.configure(userHexEncodedPublicKey, userPrivateKey, apiDB) + // Kovenant propagates a context to chained promises, so LokiPublicChatAPI.sharedContext should be used for all of the below + val promise = api.getMessages(group.channel, group.server).bind(PublicChatAPI.sharedContext) { messages -> + Promise.of(messages) + } + promise.successBackground { messages -> + // Process messages in the background + messages.forEach { message -> + // If the sender of the current message is not a slave device, set the display name in the database + val senderDisplayName = "${message.displayName} (...${message.senderPublicKey.takeLast(8)})" + DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.senderPublicKey, senderDisplayName) + val senderHexEncodedPublicKey = message.senderPublicKey + val serviceDataMessage = getDataMessage(message) + val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.serverTimestamp, false) + if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { + PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) + } else { + PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) + } + // Update profile picture if needed + val senderAsRecipient = Recipient.from(context, Address.fromSerialized(senderHexEncodedPublicKey), false) + if (message.profilePicture != null && message.profilePicture!!.url.isNotEmpty()) { + val profileKey = message.profilePicture!!.profileKey + val url = message.profilePicture!!.url + if (senderAsRecipient.profileKey == null || !MessageDigest.isEqual(senderAsRecipient.profileKey, profileKey)) { + val database = DatabaseFactory.getRecipientDatabase(context) + database.setProfileKey(senderAsRecipient, profileKey) + ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderAsRecipient, url)) + } + } + } + isCaughtUp = true + isPollOngoing = false + } + promise.fail { + Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.") + isPollOngoing = false + } + return promise.map { Unit } + } + + private fun pollForDisplayNames() { + if (displayNameUpdatees.isEmpty()) { return } + val hexEncodedPublicKeys = displayNameUpdatees + displayNameUpdatees = setOf() + api.getDisplayNames(hexEncodedPublicKeys, group.server).successBackground { mapping -> + for (pair in mapping.entries) { + val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})" + DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, pair.key, senderDisplayName) + } + }.fail { + displayNameUpdatees = displayNameUpdatees.union(hexEncodedPublicKeys) + } + } + + private fun pollForDeletedMessages() { + api.getDeletedMessageServerIDs(group.channel, group.server).success { deletedMessageServerIDs -> + val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context) + val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { lokiMessageDatabase.getMessageID(it) } + val smsMessageDatabase = DatabaseFactory.getSmsDatabase(context) + val mmsMessageDatabase = DatabaseFactory.getMmsDatabase(context) + deletedMessageIDs.forEach { + smsMessageDatabase.deleteMessage(it) + mmsMessageDatabase.delete(it) + } + }.fail { + Log.d("Loki", "Failed to get deleted messages for group chat with ID: ${group.channel} on server: ${group.server}.") + } + } + + private fun pollForModerators() { + api.getModerators(group.channel, group.server) + } + // endregion +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index 8efcbd8806..6e42ec23c3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -130,7 +130,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS val context = MessagingConfiguration.shared.context // Update profile if needed val newProfile = message.profile - if (newProfile != null && openGroupID.isNullOrEmpty()) { + if (newProfile != null) { val profileManager = SSKEnvironment.shared.profileManager val recipient = Recipient.from(context, Address.fromSerialized(message.sender!!), false) val displayName = newProfile.displayName!! diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index 5bbae93ca6..389dc370a8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -83,7 +83,10 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ messages.forEach { message -> try { val senderPublicKey = message.senderPublicKey - val senderDisplayName = message.displayName + fun generateDisplayName(rawDisplayName: String): String { + return "$rawDisplayName (...${senderPublicKey.takeLast(8)})" + } + val senderDisplayName = MessagingConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.channel, openGroup.server) ?: generateDisplayName(message.displayName) val id = openGroup.id.toByteArray() // Main message val dataMessageProto = DataMessage.newBuilder() @@ -187,6 +190,7 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ Log.e("Loki", "Exception parsing message", e) } } + displayNameUpdates = displayNameUpdates + messages.map { it.senderPublicKey }.toSet() - userHexEncodedPublicKey isCaughtUp = true isPollOngoing = false deferred.resolve(Unit) From b6769ffddc007e7ed6d4d8788509ee6c38408328 Mon Sep 17 00:00:00 2001 From: jubb Date: Wed, 31 Mar 2021 10:54:37 +1100 Subject: [PATCH 39/51] refactor: convert runnables to method references, schedule download of display names after parsing messages --- .../sending_receiving/pollers/OpenGroupPoller.kt | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index 389dc370a8..b4393046e0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -30,13 +30,6 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ private var displayNameUpdates = setOf() // endregion - // region Tasks - private val pollForNewMessagesTask = Runnable { pollForNewMessages() } - private val pollForDeletedMessagesTask = Runnable { pollForDeletedMessages() } - private val pollForModeratorsTask = Runnable { pollForModerators() } - private val pollForDisplayNamesTask = Runnable { pollForDisplayNames() } - // endregion - // region Settings companion object { private val pollForNewMessagesInterval: Long = 10 * 1000 @@ -50,10 +43,10 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ fun startIfNeeded() { if (hasStarted || executorService == null) return cancellableFutures += listOf( - executorService.scheduleAtFixedRate(pollForNewMessagesTask,0, pollForNewMessagesInterval, TimeUnit.MILLISECONDS), - executorService.scheduleAtFixedRate(pollForDeletedMessagesTask,0, pollForDeletedMessagesInterval, TimeUnit.MILLISECONDS), - executorService.scheduleAtFixedRate(pollForModeratorsTask,0, pollForModeratorsInterval, TimeUnit.MILLISECONDS), - executorService.scheduleAtFixedRate(pollForDisplayNamesTask,0, pollForDisplayNamesInterval, TimeUnit.MILLISECONDS) + executorService.scheduleAtFixedRate(::pollForNewMessages,0, pollForNewMessagesInterval, TimeUnit.MILLISECONDS), + executorService.scheduleAtFixedRate(::pollForDeletedMessages,0, pollForDeletedMessagesInterval, TimeUnit.MILLISECONDS), + executorService.scheduleAtFixedRate(::pollForModerators,0, pollForModeratorsInterval, TimeUnit.MILLISECONDS), + executorService.scheduleAtFixedRate(::pollForDisplayNames,0, pollForDisplayNamesInterval, TimeUnit.MILLISECONDS) ) hasStarted = true } @@ -191,6 +184,7 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ } } displayNameUpdates = displayNameUpdates + messages.map { it.senderPublicKey }.toSet() - userHexEncodedPublicKey + executorService?.schedule(::pollForDisplayNames, 0, TimeUnit.MILLISECONDS) isCaughtUp = true isPollOngoing = false deferred.resolve(Unit) From 7121aa85fbea03dd842361ad4ba464bee09f3224 Mon Sep 17 00:00:00 2001 From: jubb Date: Wed, 31 Mar 2021 15:15:50 +1100 Subject: [PATCH 40/51] refactor: use OpenGroupAPI.kt instead of PublicChatAPI.kt for all open group operations. Syncing open group notify conversation list listeners debounced so UI doesn't freeze on initial syncs --- .../securesms/ApplicationContext.java | 41 +- .../securesms/database/Database.java | 9 +- .../securesms/loki/activities/HomeActivity.kt | 6 +- .../loki/activities/SettingsActivity.kt | 8 +- .../securesms/loki/api/PublicChatManager.kt | 22 +- .../securesms/loki/api/PublicChatPoller.kt | 238 ----------- .../loki/utilities/OpenGroupUtilities.kt | 34 +- .../loki/views/MentionCandidateView.kt | 6 +- .../messaging/opengroups/OpenGroup.kt | 4 + .../messaging/opengroups/OpenGroupAPI.kt | 19 +- .../sending_receiving/MessageReceiver.kt | 1 - .../libsession/utilities/Debouncer.java | 5 + .../loki/api/opengroups/PublicChatAPI.kt | 386 ------------------ 13 files changed, 73 insertions(+), 706 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt delete mode 100644 libsignal/src/main/java/org/session/libsignal/service/loki/api/opengroups/PublicChatAPI.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index c07dca4f7d..ec89c1028f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -21,9 +21,9 @@ import android.content.Intent; import android.os.AsyncTask; import android.os.Build; import android.os.Handler; +import android.os.Looper; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ProcessLifecycleOwner; @@ -33,6 +33,7 @@ import org.conscrypt.Conscrypt; import org.session.libsession.messaging.MessagingConfiguration; import org.session.libsession.messaging.avatars.AvatarHelper; import org.session.libsession.messaging.jobs.JobQueue; +import org.session.libsession.messaging.opengroups.OpenGroupAPI; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPoller; import org.session.libsession.messaging.sending_receiving.pollers.Poller; @@ -50,14 +51,12 @@ import org.session.libsignal.service.loki.api.PushNotificationAPI; import org.session.libsignal.service.loki.api.SnodeAPI; import org.session.libsignal.service.loki.api.SwarmAPI; import org.session.libsignal.service.loki.api.fileserver.FileServerAPI; -import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI; import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol; import org.session.libsignal.service.loki.utilities.mentions.MentionsManager; import org.session.libsignal.utilities.logging.Log; import org.signal.aesgcmprovider.AesGcmProvider; import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule; import org.thoughtcrime.securesms.jobmanager.DependencyInjector; @@ -140,10 +139,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc public Poller poller = null; public ClosedGroupPoller closedGroupPoller = null; public PublicChatManager publicChatManager = null; - private PublicChatAPI publicChatAPI = null; public Broadcaster broadcaster = null; public SignalCommunicationModule communicationModule; private Job firebaseInstanceIdJob; + private Handler threadNotificationHandler; private volatile boolean isAppVisible; @@ -151,7 +150,11 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc return (ApplicationContext) context.getApplicationContext(); } - @Override + public Handler getThreadNotificationHandler() { + return this.threadNotificationHandler; + } + +@Override public void onCreate() { super.onCreate(); Log.i(TAG, "onCreate()"); @@ -166,6 +169,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc // ======== messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier()); broadcaster = new Broadcaster(this); + threadNotificationHandler = new Handler(Looper.getMainLooper()); LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this); LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(this); LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this); @@ -285,22 +289,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc } // Loki - public @Nullable - PublicChatAPI getPublicChatAPI() { - if (publicChatAPI != null || !IdentityKeyUtil.hasIdentityKey(this)) { - return publicChatAPI; - } - String userPublicKey = TextSecurePreferences.getLocalNumber(this); - if (userPublicKey == null) { - return publicChatAPI; - } - byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize(); - LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this); - LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this); - GroupDatabase groupDB = DatabaseFactory.getGroupDatabase(this); - publicChatAPI = new PublicChatAPI(userPublicKey, userPrivateKey, apiDB, userDB, groupDB); - return publicChatAPI; - } private void initializeSecurityProvider() { try { @@ -531,21 +519,12 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc public void updateOpenGroupProfilePicturesIfNeeded() { AsyncTask.execute(() -> { - PublicChatAPI publicChatAPI = null; - try { - publicChatAPI = getPublicChatAPI(); - } catch (Exception e) { - // Do nothing - } - if (publicChatAPI == null) { - return; - } byte[] profileKey = ProfileKeyUtil.getProfileKey(this); String url = TextSecurePreferences.getProfilePictureURL(this); Set servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers(); for (String server : servers) { if (profileKey != null) { - publicChatAPI.setProfilePicture(server, profileKey, url); + OpenGroupAPI.setProfilePicture(server, profileKey, url); } } }); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java index 4a1161fdda..cab13138aa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java @@ -16,11 +16,15 @@ */ package org.thoughtcrime.securesms.database; +import android.annotation.SuppressLint; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; + import androidx.annotation.NonNull; +import org.session.libsession.utilities.Debouncer; +import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import java.util.Set; @@ -31,10 +35,13 @@ public abstract class Database { protected SQLCipherOpenHelper databaseHelper; protected final Context context; + private final Debouncer threadNotificationDebouncer; + @SuppressLint("WrongConstant") public Database(Context context, SQLCipherOpenHelper databaseHelper) { this.context = context; this.databaseHelper = databaseHelper; + this.threadNotificationDebouncer = new Debouncer(ApplicationContext.getInstance(context).getThreadNotificationHandler(), 1000); } protected void notifyConversationListeners(Set threadIds) { @@ -47,7 +54,7 @@ public abstract class Database { } protected void notifyConversationListListeners() { - context.getContentResolver().notifyChange(DatabaseContentProviders.ConversationList.CONTENT_URI, null); + threadNotificationDebouncer.publish(()->context.getContentResolver().notifyChange(DatabaseContentProviders.ConversationList.CONTENT_URI, null)); } protected void notifyStickerListeners() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 64116414bc..8ed97bf3e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -26,11 +26,12 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R -import org.session.libsession.messaging.sending_receiving.MessageSender import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import org.session.libsession.messaging.jobs.JobQueue +import org.session.libsession.messaging.opengroups.OpenGroupAPI +import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.utilities.* import org.session.libsignal.service.loki.utilities.mentions.MentionsManager import org.session.libsignal.service.loki.utilities.toHexString @@ -359,8 +360,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server) apiDB.clearOpenGroupProfilePictureURL(publicChat.channel, publicChat.server) - ApplicationContext.getInstance(context).publicChatAPI!! - .leave(publicChat.channel, publicChat.server) + OpenGroupAPI.leave(publicChat.channel, publicChat.server) ApplicationContext.getInstance(context).publicChatManager .removeChat(publicChat.server, publicChat.channel) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt index cacc35a975..6bd6bb85ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt @@ -28,6 +28,7 @@ import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.task import nl.komponents.kovenant.ui.alwaysUi import org.session.libsession.messaging.avatars.AvatarHelper +import org.session.libsession.messaging.opengroups.OpenGroupAPI import org.session.libsession.messaging.threads.Address import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol import org.session.libsession.utilities.TextSecurePreferences @@ -179,11 +180,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { val promises = mutableListOf>() val displayName = displayNameToBeUploaded if (displayName != null) { - val publicChatAPI = ApplicationContext.getInstance(this).publicChatAPI - if (publicChatAPI != null) { - val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers() - promises.addAll(servers.map { publicChatAPI.setDisplayName(displayName, it) }) - } + val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers() + promises.addAll(servers.map { OpenGroupAPI.setDisplayName(displayName, it) }) TextSecurePreferences.setProfileName(this, displayName) } val profilePicture = profilePictureToBeUploaded diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt index c069cdfe26..c2200ac0a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt @@ -7,12 +7,12 @@ import android.text.TextUtils import androidx.annotation.WorkerThread import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.opengroups.OpenGroup +import org.session.libsession.messaging.opengroups.OpenGroupAPI +import org.session.libsession.messaging.opengroups.OpenGroupInfo import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.Util import org.session.libsignal.service.loki.api.opengroups.PublicChat -import org.session.libsignal.service.loki.api.opengroups.PublicChatInfo -import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.groups.GroupManager @@ -65,27 +65,23 @@ class PublicChatManager(private val context: Context) { //TODO Declare a specific type of checked exception instead of "Exception". @WorkerThread @Throws(java.lang.Exception::class) - public fun addChat(server: String, channel: Long): PublicChat { - val groupChatAPI = ApplicationContext.getInstance(context).publicChatAPI - ?: throw IllegalStateException("LokiPublicChatAPI is not set!") - + public fun addChat(server: String, channel: Long): OpenGroup { // Ensure the auth token is acquired. - groupChatAPI.getAuthToken(server).get() + OpenGroupAPI.getAuthToken(server).get() - val channelInfo = groupChatAPI.getChannelInfo(channel, server).get() + val channelInfo = OpenGroupAPI.getChannelInfo(channel, server).get() return addChat(server, channel, channelInfo) } @WorkerThread - public fun addChat(server: String, channel: Long, info: PublicChatInfo): PublicChat { + public fun addChat(server: String, channel: Long, info: OpenGroupInfo): OpenGroup { val chat = PublicChat(channel, server, info.displayName, true) var threadID = GroupManager.getOpenGroupThreadID(chat.id, context) var profilePicture: Bitmap? = null // Create the group if we don't have one if (threadID < 0) { if (info.profilePictureURL.isNotEmpty()) { - val profilePictureAsByteArray = ApplicationContext.getInstance(context).publicChatAPI - ?.downloadOpenGroupProfilePicture(server, info.profilePictureURL) + val profilePictureAsByteArray = OpenGroupAPI.downloadOpenGroupProfilePicture(server, info.profilePictureURL) profilePicture = BitmapUtil.fromByteArray(profilePictureAsByteArray) } val result = GroupManager.createOpenGroup(chat.id, context, profilePicture, chat.displayName) @@ -95,12 +91,12 @@ class PublicChatManager(private val context: Context) { // Set our name on the server val displayName = TextSecurePreferences.getProfileName(context) if (!TextUtils.isEmpty(displayName)) { - ApplicationContext.getInstance(context).publicChatAPI?.setDisplayName(displayName, server) + OpenGroupAPI.setDisplayName(displayName, server) } // Start polling Util.runOnMain { startPollersIfNeeded() } - return chat + return OpenGroup.from(chat) } public fun removeChat(server: String, channel: Long) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt deleted file mode 100644 index 87c0e29570..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt +++ /dev/null @@ -1,238 +0,0 @@ -package org.thoughtcrime.securesms.loki.api - -import android.content.Context -import android.os.Handler -import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.functional.bind -import nl.komponents.kovenant.functional.map -import org.session.libsession.messaging.threads.Address -import org.session.libsession.messaging.threads.recipients.Recipient -import org.session.libsession.utilities.IdentityKeyUtil -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.libsignal.util.guava.Optional -import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer -import org.session.libsignal.service.api.messages.SignalServiceContent -import org.session.libsignal.service.api.messages.SignalServiceDataMessage -import org.session.libsignal.service.api.messages.SignalServiceGroup -import org.session.libsignal.service.api.push.SignalServiceAddress -import org.session.libsignal.service.loki.api.fileserver.FileServerAPI -import org.session.libsignal.service.loki.api.opengroups.PublicChat -import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI -import org.session.libsignal.service.loki.api.opengroups.PublicChatMessage -import org.session.libsignal.utilities.logging.Log -import org.session.libsignal.utilities.successBackground -import org.thoughtcrime.securesms.ApplicationContext -import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.jobs.PushDecryptJob -import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob -import java.security.MessageDigest -import java.util.* - -class PublicChatPoller(private val context: Context, private val group: PublicChat) { - private val handler by lazy { Handler() } - private var hasStarted = false - private var isPollOngoing = false - public var isCaughtUp = false - - // region Convenience - private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)!! - private var displayNameUpdatees = setOf() - - private val api: PublicChatAPI - get() = { - val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() - val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context) - val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context) - val openGroupDatabase = DatabaseFactory.getGroupDatabase(context) - PublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase, openGroupDatabase) - }() - // endregion - - // region Tasks - private val pollForNewMessagesTask = object : Runnable { - - override fun run() { - pollForNewMessages() - handler.postDelayed(this, pollForNewMessagesInterval) - } - } - - private val pollForDeletedMessagesTask = object : Runnable { - - override fun run() { - pollForDeletedMessages() - handler.postDelayed(this, pollForDeletedMessagesInterval) - } - } - - private val pollForModeratorsTask = object : Runnable { - - override fun run() { - pollForModerators() - handler.postDelayed(this, pollForModeratorsInterval) - } - } - - private val pollForDisplayNamesTask = object : Runnable { - - override fun run() { - pollForDisplayNames() - handler.postDelayed(this, pollForDisplayNamesInterval) - } - } - // endregion - - // region Settings - companion object { - private val pollForNewMessagesInterval: Long = 4 * 1000 - private val pollForDeletedMessagesInterval: Long = 60 * 1000 - private val pollForModeratorsInterval: Long = 10 * 60 * 1000 - private val pollForDisplayNamesInterval: Long = 60 * 1000 - } - // endregion - - // region Lifecycle - fun startIfNeeded() { - if (hasStarted) return - pollForNewMessagesTask.run() - pollForDeletedMessagesTask.run() - pollForModeratorsTask.run() - pollForDisplayNamesTask.run() - hasStarted = true - } - - fun stop() { - handler.removeCallbacks(pollForNewMessagesTask) - handler.removeCallbacks(pollForDeletedMessagesTask) - handler.removeCallbacks(pollForModeratorsTask) - handler.removeCallbacks(pollForDisplayNamesTask) - hasStarted = false - } - // endregion - - // region Polling - private fun getDataMessage(message: PublicChatMessage): SignalServiceDataMessage { - val id = group.id.toByteArray() - val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.PUBLIC_CHAT, null, null, null, null) - val quote = if (message.quote != null) { - SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteePublicKey), message.quote!!.quotedMessageBody, listOf()) - } else { - null - } - val attachments = message.attachments.mapNotNull { attachment -> - if (attachment.kind != PublicChatMessage.Attachment.Kind.Attachment) { return@mapNotNull null } - SignalServiceAttachmentPointer( - attachment.serverID, - attachment.contentType, - ByteArray(0), - Optional.of(attachment.size), - Optional.absent(), - attachment.width, attachment.height, - Optional.absent(), - Optional.of(attachment.fileName), - false, - Optional.fromNullable(attachment.caption), - attachment.url) - } - val linkPreview = message.attachments.firstOrNull { it.kind == PublicChatMessage.Attachment.Kind.LinkPreview } - val signalLinkPreviews = mutableListOf() - if (linkPreview != null) { - val attachment = SignalServiceAttachmentPointer( - linkPreview.serverID, - linkPreview.contentType, - ByteArray(0), - Optional.of(linkPreview.size), - Optional.absent(), - linkPreview.width, linkPreview.height, - Optional.absent(), - Optional.of(linkPreview.fileName), - false, - Optional.fromNullable(linkPreview.caption), - linkPreview.url) - signalLinkPreviews.add(SignalServiceDataMessage.Preview(linkPreview.linkPreviewURL!!, linkPreview.linkPreviewTitle!!, Optional.of(attachment))) - } - val body = if (message.body == message.timestamp.toString()) "" else message.body // Workaround for the fact that the back-end doesn't accept messages without a body - val syncTarget = if (message.senderPublicKey == userHexEncodedPublicKey) group.id else null - return SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, 0, false, null, quote, null, signalLinkPreviews, null, syncTarget) - } - - fun pollForNewMessages(): Promise { - if (isPollOngoing) { return Promise.of(Unit) } - isPollOngoing = true - val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() - val apiDB = DatabaseFactory.getLokiAPIDatabase(context) - FileServerAPI.configure(userHexEncodedPublicKey, userPrivateKey, apiDB) - // Kovenant propagates a context to chained promises, so LokiPublicChatAPI.sharedContext should be used for all of the below - val promise = api.getMessages(group.channel, group.server).bind(PublicChatAPI.sharedContext) { messages -> - Promise.of(messages) - } - promise.successBackground { messages -> - // Process messages in the background - messages.forEach { message -> - // If the sender of the current message is not a slave device, set the display name in the database - val senderDisplayName = "${message.displayName} (...${message.senderPublicKey.takeLast(8)})" - DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.senderPublicKey, senderDisplayName) - val senderHexEncodedPublicKey = message.senderPublicKey - val serviceDataMessage = getDataMessage(message) - val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.serverTimestamp, false) - if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { - PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) - } else { - PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) - } - // Update profile picture if needed - val senderAsRecipient = Recipient.from(context, Address.fromSerialized(senderHexEncodedPublicKey), false) - if (message.profilePicture != null && message.profilePicture!!.url.isNotEmpty()) { - val profileKey = message.profilePicture!!.profileKey - val url = message.profilePicture!!.url - if (senderAsRecipient.profileKey == null || !MessageDigest.isEqual(senderAsRecipient.profileKey, profileKey)) { - val database = DatabaseFactory.getRecipientDatabase(context) - database.setProfileKey(senderAsRecipient, profileKey) - ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderAsRecipient, url)) - } - } - } - isCaughtUp = true - isPollOngoing = false - } - promise.fail { - Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.") - isPollOngoing = false - } - return promise.map { Unit } - } - - private fun pollForDisplayNames() { - if (displayNameUpdatees.isEmpty()) { return } - val hexEncodedPublicKeys = displayNameUpdatees - displayNameUpdatees = setOf() - api.getDisplayNames(hexEncodedPublicKeys, group.server).successBackground { mapping -> - for (pair in mapping.entries) { - val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})" - DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, pair.key, senderDisplayName) - } - }.fail { - displayNameUpdatees = displayNameUpdatees.union(hexEncodedPublicKeys) - } - } - - private fun pollForDeletedMessages() { - api.getDeletedMessageServerIDs(group.channel, group.server).success { deletedMessageServerIDs -> - val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context) - val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { lokiMessageDatabase.getMessageID(it) } - val smsMessageDatabase = DatabaseFactory.getSmsDatabase(context) - val mmsMessageDatabase = DatabaseFactory.getMmsDatabase(context) - deletedMessageIDs.forEach { - smsMessageDatabase.deleteMessage(it) - mmsMessageDatabase.delete(it) - } - }.fail { - Log.d("Loki", "Failed to get deleted messages for group chat with ID: ${group.channel} on server: ${group.server}.") - } - } - - private fun pollForModerators() { - api.getModerators(group.channel, group.server) - } - // endregion -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt index 45c792931a..c6b789f996 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt @@ -3,16 +3,15 @@ package org.thoughtcrime.securesms.loki.utilities import android.content.Context import androidx.annotation.WorkerThread import org.greenrobot.eventbus.EventBus -import org.thoughtcrime.securesms.ApplicationContext -import org.session.libsession.utilities.preferences.ProfileKeyUtil -import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.groups.GroupManager +import org.session.libsession.messaging.opengroups.OpenGroup +import org.session.libsession.messaging.opengroups.OpenGroupAPI import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.preferences.ProfileKeyUtil import org.session.libsignal.service.loki.api.opengroups.PublicChat -import java.lang.Exception -import java.lang.IllegalStateException -import kotlin.jvm.Throws +import org.thoughtcrime.securesms.ApplicationContext +import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.groups.GroupManager //TODO Refactor so methods declare specific type of checked exceptions and not generalized Exception. object OpenGroupUtilities { @@ -22,29 +21,27 @@ object OpenGroupUtilities { @JvmStatic @WorkerThread @Throws(Exception::class) - fun addGroup(context: Context, url: String, channel: Long): PublicChat { + fun addGroup(context: Context, url: String, channel: Long): OpenGroup { // Check for an existing group. val groupID = PublicChat.getId(channel, url) val threadID = GroupManager.getOpenGroupThreadID(groupID, context) val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID) - if (openGroup != null) { return openGroup } + if (openGroup != null) { return OpenGroup.from(openGroup) } // Add the new group. val application = ApplicationContext.getInstance(context) val displayName = TextSecurePreferences.getProfileName(context) - val lokiPublicChatAPI = application.publicChatAPI - ?: throw IllegalStateException("LokiPublicChatAPI is not initialized.") val group = application.publicChatManager.addChat(url, channel) DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(channel, url) DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(channel, url) - lokiPublicChatAPI.getMessages(channel, url) - lokiPublicChatAPI.setDisplayName(displayName, url) - lokiPublicChatAPI.join(channel, url) + OpenGroupAPI.getMessages(channel, url) + OpenGroupAPI.setDisplayName(displayName, url) + OpenGroupAPI.join(channel, url) val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(context) val profileUrl: String? = TextSecurePreferences.getProfilePictureURL(context) - lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl) + OpenGroupAPI.setProfilePicture(url, profileKey, profileUrl) return group } @@ -58,18 +55,15 @@ object OpenGroupUtilities { @WorkerThread @Throws(Exception::class) fun updateGroupInfo(context: Context, url: String, channel: Long) { - val publicChatAPI = ApplicationContext.getInstance(context).publicChatAPI - ?: throw IllegalStateException("Public chat API is not initialized!") - // Check if open group has a related DB record. val groupId = GroupUtil.getEncodedOpenGroupID(PublicChat.getId(channel, url).toByteArray()) if (!DatabaseFactory.getGroupDatabase(context).hasGroup(groupId)) { throw IllegalStateException("Attempt to update open group info for non-existent DB record: $groupId") } - val info = publicChatAPI.getChannelInfo(channel, url).get() + val info = OpenGroupAPI.getChannelInfo(channel, url).get() - publicChatAPI.updateProfileIfNeeded(channel, url, groupId, info, false) + OpenGroupAPI.updateProfileIfNeeded(channel, url, groupId, info, false) EventBus.getDefault().post(GroupInfoUpdatedEvent(url, channel)) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt index d33ff9f34f..f85e5e9867 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt @@ -8,9 +8,9 @@ import android.view.ViewGroup import android.widget.LinearLayout import kotlinx.android.synthetic.main.view_mention_candidate.view.* import network.loki.messenger.R -import org.thoughtcrime.securesms.mms.GlideRequests -import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI +import org.session.libsession.messaging.opengroups.OpenGroupAPI import org.session.libsignal.service.loki.utilities.mentions.Mention +import org.thoughtcrime.securesms.mms.GlideRequests class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) { var mentionCandidate = Mention("", "") @@ -38,7 +38,7 @@ class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: profilePictureView.glide = glide!! profilePictureView.update() if (publicChatServer != null && publicChatChannel != null) { - val isUserModerator = PublicChatAPI.isUserModerator(mentionCandidate.publicKey, publicChatChannel!!, publicChatServer!!) + val isUserModerator = OpenGroupAPI.isUserModerator(mentionCandidate.publicKey, publicChatChannel!!, publicChatServer!!) moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE } else { moderatorIconImageView.visibility = View.GONE diff --git a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroup.kt b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroup.kt index 81ea7abcbb..c0a48274cd 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroup.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroup.kt @@ -1,5 +1,6 @@ package org.session.libsession.messaging.opengroups +import org.session.libsignal.service.loki.api.opengroups.PublicChat import org.session.libsignal.utilities.JsonUtil data class OpenGroup( @@ -13,6 +14,9 @@ data class OpenGroup( companion object { + @JvmStatic fun from(publicChat: PublicChat): OpenGroup = + OpenGroup(publicChat.channel, publicChat.server, publicChat.displayName, publicChat.isDeletable) + @JvmStatic fun getId(channel: Long, server: String): String { return "$server.$channel" } diff --git a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPI.kt index c8c53dc6bd..6c35888f7b 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/opengroups/OpenGroupAPI.kt @@ -6,15 +6,13 @@ import nl.komponents.kovenant.deferred import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.then import org.session.libsession.messaging.MessagingConfiguration - -import org.session.libsession.messaging.utilities.DotNetAPI import org.session.libsession.messaging.fileserver.FileServerAPI - -import org.session.libsignal.utilities.logging.Log -import org.session.libsignal.utilities.* +import org.session.libsession.messaging.utilities.DotNetAPI import org.session.libsignal.service.loki.utilities.DownloadUtilities import org.session.libsignal.service.loki.utilities.retryIfNeeded +import org.session.libsignal.utilities.* import org.session.libsignal.utilities.Base64 +import org.session.libsignal.utilities.logging.Log import java.io.ByteArrayOutputStream import java.text.SimpleDateFormat import java.util.* @@ -156,6 +154,7 @@ object OpenGroupAPI: DotNetAPI() { } } + @JvmStatic fun getDeletedMessageServerIDs(channel: Long, server: String): Promise, Exception> { Log.d("Loki", "Getting deleted messages for open group with ID: $channel on server: $server.") val storage = MessagingConfiguration.shared.storage @@ -188,6 +187,7 @@ object OpenGroupAPI: DotNetAPI() { } } + @JvmStatic fun sendMessage(message: OpenGroupMessage, channel: Long, server: String): Promise { val deferred = deferred() val storage = MessagingConfiguration.shared.storage @@ -252,6 +252,7 @@ object OpenGroupAPI: DotNetAPI() { } } + @JvmStatic fun getModerators(channel: Long, server: String): Promise, Exception> { return execute(HTTPVerb.GET, server, "loki/v1/channel/$channel/get_moderators").then(sharedContext) { json -> try { @@ -270,6 +271,7 @@ object OpenGroupAPI: DotNetAPI() { } } + @JvmStatic fun getChannelInfo(channel: Long, server: String): Promise { return retryIfNeeded(maxRetryCount) { val parameters = mapOf( "include_annotations" to 1 ) @@ -294,6 +296,7 @@ object OpenGroupAPI: DotNetAPI() { } } + @JvmStatic fun updateProfileIfNeeded(channel: Long, server: String, groupID: String, info: OpenGroupInfo, isForcedUpdate: Boolean) { val storage = MessagingConfiguration.shared.storage storage.setUserCount(channel, server, info.memberCount) @@ -307,6 +310,7 @@ object OpenGroupAPI: DotNetAPI() { } } + @JvmStatic fun downloadOpenGroupProfilePicture(server: String, endpoint: String): ByteArray? { val url = "${server.removeSuffix("/")}/${endpoint.removePrefix("/")}" Log.d("Loki", "Downloading open group profile picture from \"$url\".") @@ -323,6 +327,7 @@ object OpenGroupAPI: DotNetAPI() { } } + @JvmStatic fun join(channel: Long, server: String): Promise { return retryIfNeeded(maxRetryCount) { execute(HTTPVerb.POST, server, "/channels/$channel/subscribe").then { @@ -331,6 +336,7 @@ object OpenGroupAPI: DotNetAPI() { } } + @JvmStatic fun leave(channel: Long, server: String): Promise { return retryIfNeeded(maxRetryCount) { execute(HTTPVerb.DELETE, server, "/channels/$channel/subscribe").then { @@ -348,6 +354,7 @@ object OpenGroupAPI: DotNetAPI() { } } + @JvmStatic fun getDisplayNames(publicKeys: Set, server: String): Promise, Exception> { return getUserProfiles(publicKeys, server, false).map(sharedContext) { json -> val mapping = mutableMapOf() @@ -362,12 +369,14 @@ object OpenGroupAPI: DotNetAPI() { } } + @JvmStatic fun setDisplayName(newDisplayName: String?, server: String): Promise { Log.d("Loki", "Updating display name on server: $server.") val parameters = mapOf( "name" to (newDisplayName ?: "") ) return execute(HTTPVerb.PATCH, server, "users/me", parameters = parameters).map { Unit } } + @JvmStatic fun setProfilePicture(server: String, profileKey: ByteArray, url: String?): Promise { return setProfilePicture(server, Base64.encodeBytes(profileKey), url) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index e1b7e652ef..216d79627c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -101,7 +101,6 @@ object MessageReceiver { } // Don't process the envelope any further if the message has been handled already if (storage.isMessageDuplicated(envelope.timestamp, sender!!) && !isRetry) throw Error.DuplicateMessage - storage.addReceivedMessageTimestamp(envelope.timestamp) // Don't process the envelope any further if the sender is blocked if (isBlock(sender!!)) throw Error.SenderBlocked // Parse the proto diff --git a/libsession/src/main/java/org/session/libsession/utilities/Debouncer.java b/libsession/src/main/java/org/session/libsession/utilities/Debouncer.java index 0bf82e3e07..84312a4c2c 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/Debouncer.java +++ b/libsession/src/main/java/org/session/libsession/utilities/Debouncer.java @@ -25,6 +25,11 @@ public class Debouncer { this.threshold = threshold; } + public Debouncer(Handler handler, long threshold) { + this.handler = handler; + this.threshold = threshold; + } + public void publish(Runnable runnable) { handler.removeCallbacksAndMessages(null); handler.postDelayed(runnable, threshold); diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/api/opengroups/PublicChatAPI.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/api/opengroups/PublicChatAPI.kt deleted file mode 100644 index 3a4c58db0b..0000000000 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/api/opengroups/PublicChatAPI.kt +++ /dev/null @@ -1,386 +0,0 @@ -package org.session.libsignal.service.loki.api.opengroups - -import nl.komponents.kovenant.Kovenant -import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.deferred -import nl.komponents.kovenant.functional.map -import nl.komponents.kovenant.then -import org.session.libsignal.utilities.logging.Log -import org.session.libsignal.utilities.Base64 -import org.session.libsignal.utilities.Hex -import org.session.libsignal.utilities.JsonUtil -import org.session.libsignal.service.loki.api.LokiDotNetAPI -import org.session.libsignal.service.loki.api.SnodeAPI -import org.session.libsignal.service.loki.api.fileserver.FileServerAPI -import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol -import org.session.libsignal.service.loki.database.LokiOpenGroupDatabaseProtocol -import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol -import org.session.libsignal.service.loki.utilities.DownloadUtilities -import org.session.libsignal.utilities.* -import org.session.libsignal.service.loki.utilities.retryIfNeeded -import java.io.ByteArrayOutputStream -import java.text.SimpleDateFormat -import java.util.* - -class PublicChatAPI(userPublicKey: String, private val userPrivateKey: ByteArray, private val apiDatabase: LokiAPIDatabaseProtocol, - private val userDatabase: LokiUserDatabaseProtocol, private val openGroupDatabase: LokiOpenGroupDatabaseProtocol) : LokiDotNetAPI(userPublicKey, userPrivateKey, apiDatabase) { - - companion object { - private val moderators: HashMap>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs) - val sharedContext = Kovenant.createContext() - - // region Settings - private val fallbackBatchCount = 64 - private val maxRetryCount = 8 - // endregion - - // region Convenience - private val channelInfoType = "net.patter-app.settings" - private val attachmentType = "net.app.core.oembed" - @JvmStatic - val publicChatMessageType = "network.loki.messenger.publicChat" - @JvmStatic - val profilePictureType = "network.loki.messenger.avatar" - - fun getDefaultChats(): List { - return listOf() // Don't auto-join any open groups right now - } - - fun isUserModerator(hexEncodedPublicKey: String, channel: Long, server: String): Boolean { - if (moderators[server] != null && moderators[server]!![channel] != null) { - return moderators[server]!![channel]!!.contains(hexEncodedPublicKey) - } - return false - } - // endregion - } - - // region Public API - fun getMessages(channel: Long, server: String): Promise, Exception> { - Log.d("Loki", "Getting messages for open group with ID: $channel on server: $server.") - val parameters = mutableMapOf( "include_annotations" to 1 ) - val lastMessageServerID = apiDatabase.getLastMessageServerID(channel, server) - if (lastMessageServerID != null) { - parameters["since_id"] = lastMessageServerID - } else { - parameters["count"] = fallbackBatchCount - parameters["include_deleted"] = 0 - } - return execute(HTTPVerb.GET, server, "channels/$channel/messages", parameters = parameters).then(sharedContext) { json -> - try { - val data = json["data"] as List> - val messages = data.mapNotNull { message -> - try { - val isDeleted = message["is_deleted"] as? Boolean ?: false - if (isDeleted) { return@mapNotNull null } - // Ignore messages without annotations - if (message["annotations"] == null) { return@mapNotNull null } - val annotation = (message["annotations"] as List>).find { - ((it["type"] as? String ?: "") == publicChatMessageType) && it["value"] != null - } ?: return@mapNotNull null - val value = annotation["value"] as Map<*, *> - val serverID = message["id"] as? Long ?: (message["id"] as? Int)?.toLong() ?: (message["id"] as String).toLong() - val user = message["user"] as Map<*, *> - val publicKey = user["username"] as String - val displayName = user["name"] as? String ?: "Anonymous" - var profilePicture: PublicChatMessage.ProfilePicture? = null - if (user["annotations"] != null) { - val profilePictureAnnotation = (user["annotations"] as List>).find { - ((it["type"] as? String ?: "") == profilePictureType) && it["value"] != null - } - val profilePictureAnnotationValue = profilePictureAnnotation?.get("value") as? Map<*, *> - if (profilePictureAnnotationValue != null && profilePictureAnnotationValue["profileKey"] != null && profilePictureAnnotationValue["url"] != null) { - try { - val profileKey = Base64.decode(profilePictureAnnotationValue["profileKey"] as String) - val url = profilePictureAnnotationValue["url"] as String - profilePicture = PublicChatMessage.ProfilePicture(profileKey, url) - } catch (e: Exception) {} - } - } - @Suppress("NAME_SHADOWING") val body = message["text"] as String - val timestamp = value["timestamp"] as? Long ?: (value["timestamp"] as? Int)?.toLong() ?: (value["timestamp"] as String).toLong() - var quote: PublicChatMessage.Quote? = null - if (value["quote"] != null) { - val replyTo = message["reply_to"] as? Long ?: (message["reply_to"] as? Int)?.toLong() ?: (message["reply_to"] as String).toLong() - val quoteAnnotation = value["quote"] as? Map<*, *> - val quoteTimestamp = quoteAnnotation?.get("id") as? Long ?: (quoteAnnotation?.get("id") as? Int)?.toLong() ?: (quoteAnnotation?.get("id") as? String)?.toLong() ?: 0L - val author = quoteAnnotation?.get("author") as? String - val text = quoteAnnotation?.get("text") as? String - quote = if (quoteTimestamp > 0L && author != null && text != null) PublicChatMessage.Quote(quoteTimestamp, author, text, replyTo) else null - } - val attachmentsAsJSON = (message["annotations"] as List>).filter { - ((it["type"] as? String ?: "") == attachmentType) && it["value"] != null - } - val attachments = attachmentsAsJSON.mapNotNull { it["value"] as? Map<*, *> }.mapNotNull { attachmentAsJSON -> - try { - val kindAsString = attachmentAsJSON["lokiType"] as String - val kind = PublicChatMessage.Attachment.Kind.values().first { it.rawValue == kindAsString } - val id = attachmentAsJSON["id"] as? Long ?: (attachmentAsJSON["id"] as? Int)?.toLong() ?: (attachmentAsJSON["id"] as String).toLong() - val contentType = attachmentAsJSON["contentType"] as String - val size = attachmentAsJSON["size"] as? Int ?: (attachmentAsJSON["size"] as? Long)?.toInt() ?: (attachmentAsJSON["size"] as String).toInt() - val fileName = attachmentAsJSON["fileName"] as String - val flags = 0 - val url = attachmentAsJSON["url"] as String - val caption = attachmentAsJSON["caption"] as? String - val linkPreviewURL = attachmentAsJSON["linkPreviewUrl"] as? String - val linkPreviewTitle = attachmentAsJSON["linkPreviewTitle"] as? String - if (kind == PublicChatMessage.Attachment.Kind.LinkPreview && (linkPreviewURL == null || linkPreviewTitle == null)) { - null - } else { - PublicChatMessage.Attachment(kind, server, id, contentType, size, fileName, flags, 0, 0, caption, url, linkPreviewURL, linkPreviewTitle) - } - } catch (e: Exception) { - Log.d("Loki","Couldn't parse attachment due to error: $e.") - null - } - } - // Set the last message server ID here to avoid the situation where a message doesn't have a valid signature and this function is called over and over - @Suppress("NAME_SHADOWING") val lastMessageServerID = apiDatabase.getLastMessageServerID(channel, server) - if (serverID > lastMessageServerID ?: 0) { apiDatabase.setLastMessageServerID(channel, server, serverID) } - val hexEncodedSignature = value["sig"] as String - val signatureVersion = value["sigver"] as? Long ?: (value["sigver"] as? Int)?.toLong() ?: (value["sigver"] as String).toLong() - val signature = PublicChatMessage.Signature(Hex.fromStringCondensed(hexEncodedSignature), signatureVersion) - val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) - format.timeZone = TimeZone.getTimeZone("GMT") - val dateAsString = message["created_at"] as String - val serverTimestamp = format.parse(dateAsString).time - // Verify the message - val groupMessage = PublicChatMessage(serverID, publicKey, displayName, body, timestamp, publicChatMessageType, quote, attachments, profilePicture, signature, serverTimestamp) - if (groupMessage.hasValidSignature()) groupMessage else null - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse message for open group with ID: $channel on server: $server from: ${JsonUtil.toJson(message)}. Exception: ${exception.message}") - return@mapNotNull null - } - }.sortedBy { it.serverTimestamp } - messages - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse messages for open group with ID: $channel on server: $server.") - throw exception - } - } - } - - fun getDeletedMessageServerIDs(channel: Long, server: String): Promise, Exception> { - Log.d("Loki", "Getting deleted messages for open group with ID: $channel on server: $server.") - val parameters = mutableMapOf() - val lastDeletionServerID = apiDatabase.getLastDeletionServerID(channel, server) - if (lastDeletionServerID != null) { - parameters["since_id"] = lastDeletionServerID - } else { - parameters["count"] = fallbackBatchCount - } - return execute(HTTPVerb.GET, server, "loki/v1/channel/$channel/deletes", parameters = parameters).then(sharedContext) { json -> - try { - val deletedMessageServerIDs = (json["data"] as List>).mapNotNull { deletion -> - try { - val serverID = deletion["id"] as? Long ?: (deletion["id"] as? Int)?.toLong() ?: (deletion["id"] as String).toLong() - val messageServerID = deletion["message_id"] as? Long ?: (deletion["message_id"] as? Int)?.toLong() ?: (deletion["message_id"] as String).toLong() - @Suppress("NAME_SHADOWING") val lastDeletionServerID = apiDatabase.getLastDeletionServerID(channel, server) - if (serverID > (lastDeletionServerID ?: 0)) { apiDatabase.setLastDeletionServerID(channel, server, serverID) } - messageServerID - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse deleted message for open group with ID: $channel on server: $server. Exception: ${exception.message}") - return@mapNotNull null - } - } - deletedMessageServerIDs - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse deleted messages for open group with ID: $channel on server: $server.") - throw exception - } - } - } - - fun sendMessage(message: PublicChatMessage, channel: Long, server: String): Promise { - val deferred = deferred() - ThreadUtils.queue { - val signedMessage = message.sign(userPrivateKey) - if (signedMessage == null) { - deferred.reject(SnodeAPI.Error.MessageSigningFailed) - } else { - retryIfNeeded(maxRetryCount) { - Log.d("Loki", "Sending message to open group with ID: $channel on server: $server.") - val parameters = signedMessage.toJSON() - execute(HTTPVerb.POST, server, "channels/$channel/messages", parameters = parameters).then(sharedContext) { json -> - try { - val data = json["data"] as Map<*, *> - val serverID = (data["id"] as? Long) ?: (data["id"] as? Int)?.toLong() ?: (data["id"] as String).toLong() - val displayName = userDatabase.getDisplayName(userPublicKey) ?: "Anonymous" - val text = data["text"] as String - val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) - format.timeZone = TimeZone.getTimeZone("GMT") - val dateAsString = data["created_at"] as String - val timestamp = format.parse(dateAsString).time - @Suppress("NAME_SHADOWING") val message = PublicChatMessage(serverID, userPublicKey, displayName, text, timestamp, publicChatMessageType, message.quote, message.attachments, null, signedMessage.signature, timestamp) - message - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse message for open group with ID: $channel on server: $server.") - throw exception - } - } - }.success { - deferred.resolve(it) - }.fail { - deferred.reject(it) - } - } - } - return deferred.promise - } - - fun deleteMessage(messageServerID: Long, channel: Long, server: String, isSentByUser: Boolean): Promise { - return retryIfNeeded(maxRetryCount) { - val isModerationRequest = !isSentByUser - Log.d("Loki", "Deleting message with ID: $messageServerID from open group with ID: $channel on server: $server (isModerationRequest = $isModerationRequest).") - val endpoint = if (isSentByUser) "channels/$channel/messages/$messageServerID" else "loki/v1/moderation/message/$messageServerID" - execute(HTTPVerb.DELETE, server, endpoint, isJSONRequired = false).then { - Log.d("Loki", "Deleted message with ID: $messageServerID from open group with ID: $channel on server: $server.") - messageServerID - } - } - } - - fun deleteMessages(messageServerIDs: List, channel: Long, server: String, isSentByUser: Boolean): Promise, Exception> { - return retryIfNeeded(maxRetryCount) { - val isModerationRequest = !isSentByUser - val parameters = mapOf( "ids" to messageServerIDs.joinToString(",") ) - Log.d("Loki", "Deleting messages with IDs: ${messageServerIDs.joinToString()} from open group with ID: $channel on server: $server (isModerationRequest = $isModerationRequest).") - val endpoint = if (isSentByUser) "loki/v1/messages" else "loki/v1/moderation/messages" - execute(HTTPVerb.DELETE, server, endpoint, parameters = parameters, isJSONRequired = false).then { json -> - Log.d("Loki", "Deleted messages with IDs: $messageServerIDs from open group with ID: $channel on server: $server.") - messageServerIDs - } - } - } - - fun getModerators(channel: Long, server: String): Promise, Exception> { - return execute(HTTPVerb.GET, server, "loki/v1/channel/$channel/get_moderators").then(sharedContext) { json -> - try { - @Suppress("UNCHECKED_CAST") val moderators = json["moderators"] as? List - val moderatorsAsSet = moderators.orEmpty().toSet() - if (Companion.moderators[server] != null) { - Companion.moderators[server]!![channel] = moderatorsAsSet - } else { - Companion.moderators[server] = hashMapOf( channel to moderatorsAsSet ) - } - moderatorsAsSet - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse moderators for open group with ID: $channel on server: $server.") - throw exception - } - } - } - - fun getChannelInfo(channel: Long, server: String): Promise { - return retryIfNeeded(maxRetryCount) { - val parameters = mapOf( "include_annotations" to 1 ) - execute(HTTPVerb.GET, server, "/channels/$channel", parameters = parameters).then(sharedContext) { json -> - try { - val data = json["data"] as Map<*, *> - val annotations = data["annotations"] as List> - val annotation = annotations.find { (it["type"] as? String ?: "") == channelInfoType } ?: throw SnodeAPI.Error.ParsingFailed - val info = annotation["value"] as Map<*, *> - val displayName = info["name"] as String - val countInfo = data["counts"] as Map<*, *> - val memberCount = countInfo["subscribers"] as? Int ?: (countInfo["subscribers"] as? Long)?.toInt() ?: (countInfo["subscribers"] as String).toInt() - val profilePictureURL = info["avatar"] as String - val publicChatInfo = PublicChatInfo(displayName, profilePictureURL, memberCount) - apiDatabase.setUserCount(channel, server, memberCount) - publicChatInfo - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse info for open group with ID: $channel on server: $server.") - throw exception - } - } - } - } - - fun updateProfileIfNeeded(channel: Long, server: String, groupID: String, info: PublicChatInfo, isForcedUpdate: Boolean) { - apiDatabase.setUserCount(channel, server, info.memberCount) - openGroupDatabase.updateTitle(groupID, info.displayName) - // Download and update profile picture if needed - val oldProfilePictureURL = apiDatabase.getOpenGroupProfilePictureURL(channel, server) - if (isForcedUpdate || oldProfilePictureURL != info.profilePictureURL) { - val profilePictureAsByteArray = downloadOpenGroupProfilePicture(server, info.profilePictureURL) ?: return - openGroupDatabase.updateProfilePicture(groupID, profilePictureAsByteArray) - apiDatabase.setOpenGroupProfilePictureURL(channel, server, info.profilePictureURL) - } - } - - fun downloadOpenGroupProfilePicture(server: String, endpoint: String): ByteArray? { - val url = "${server.removeSuffix("/")}/${endpoint.removePrefix("/")}" - Log.d("Loki", "Downloading open group profile picture from \"$url\".") - val outputStream = ByteArrayOutputStream() - try { - DownloadUtilities.downloadFile(outputStream, url, FileServerAPI.maxFileSize, null) - Log.d("Loki", "Open group profile picture was successfully loaded from \"$url\"") - return outputStream.toByteArray() - } catch (e: Exception) { - Log.d("Loki", "Failed to download open group profile picture from \"$url\" due to error: $e.") - return null - } finally { - outputStream.close() - } - } - - fun join(channel: Long, server: String): Promise { - return retryIfNeeded(maxRetryCount) { - execute(HTTPVerb.POST, server, "/channels/$channel/subscribe").then { - Log.d("Loki", "Joined channel with ID: $channel on server: $server.") - } - } - } - - fun leave(channel: Long, server: String): Promise { - return retryIfNeeded(maxRetryCount) { - execute(HTTPVerb.DELETE, server, "/channels/$channel/subscribe").then { - Log.d("Loki", "Left channel with ID: $channel on server: $server.") - } - } - } - - fun ban(publicKey: String, server: String): Promise { - return retryIfNeeded(maxRetryCount) { - execute(HTTPVerb.POST, server, "/loki/v1/moderation/blacklist/@$publicKey").then { - Log.d("Loki", "Banned user with ID: $publicKey from $server") - } - } - } - - fun getDisplayNames(publicKeys: Set, server: String): Promise, Exception> { - return getUserProfiles(publicKeys, server, false).map(sharedContext) { json -> - val mapping = mutableMapOf() - for (user in json) { - if (user["username"] != null) { - val publicKey = user["username"] as String - val displayName = user["name"] as? String ?: "Anonymous" - mapping[publicKey] = displayName - } - } - mapping - } - } - - fun setDisplayName(newDisplayName: String?, server: String): Promise { - Log.d("Loki", "Updating display name on server: $server.") - val parameters = mapOf( "name" to (newDisplayName ?: "") ) - return execute(HTTPVerb.PATCH, server, "users/me", parameters = parameters).map { Unit } - } - - fun setProfilePicture(server: String, profileKey: ByteArray, url: String?): Promise { - return setProfilePicture(server, Base64.encodeBytes(profileKey), url) - } - - fun setProfilePicture(server: String, profileKey: String, url: String?): Promise { - Log.d("Loki", "Updating profile picture on server: $server.") - val value = when (url) { - null -> null - else -> mapOf( "profileKey" to profileKey, "url" to url ) - } - // TODO: This may actually completely replace the annotations, have to double check it - return setSelfAnnotation(server, profilePictureType, value).map { Unit }.fail { - Log.d("Loki", "Failed to update profile picture due to error: $it.") - } - } - // endregion -} From 5c87cef26ae541ac386d49e0181f211fad679d11 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Wed, 31 Mar 2021 17:14:51 +1100 Subject: [PATCH 41/51] fix multi device disappearing message issue --- .../messaging/messages/control/ExpirationTimerUpdate.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt index 2a80074a39..1473146237 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt @@ -10,6 +10,8 @@ class ExpirationTimerUpdate() : ControlMessage() { var syncTarget: String? = null var duration: Int? = 0 + override val isSelfSendValid: Boolean = true + companion object { const val TAG = "ExpirationTimerUpdate" From 20b5da18feaaeb819cacd03247e7acd8e5e9914c Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 1 Apr 2021 11:51:39 +1100 Subject: [PATCH 42/51] feat: lower debounce period --- .../main/java/org/thoughtcrime/securesms/database/Database.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java index cab13138aa..0736a56933 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java @@ -41,7 +41,7 @@ public abstract class Database { public Database(Context context, SQLCipherOpenHelper databaseHelper) { this.context = context; this.databaseHelper = databaseHelper; - this.threadNotificationDebouncer = new Debouncer(ApplicationContext.getInstance(context).getThreadNotificationHandler(), 1000); + this.threadNotificationDebouncer = new Debouncer(ApplicationContext.getInstance(context).getThreadNotificationHandler(), 100); } protected void notifyConversationListeners(Set threadIds) { From fe47a6e958f17aa1c98052b960f87509185f4e78 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Thu, 1 Apr 2021 15:48:35 +1100 Subject: [PATCH 43/51] manage duplicates outgoing messages --- .../securesms/database/MmsDatabase.java | 18 +++++++++++++++++ .../securesms/database/SmsDatabase.java | 20 ++++++++++++++++++- .../securesms/jobs/PushDecryptJob.java | 2 +- .../messages/signal/OutgoingTextMessage.java | 10 ++++++++-- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 6e8b85a2d3..3745735da5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -774,6 +774,11 @@ public class MmsDatabase extends MessagingDatabase { quoteAttachments.addAll(message.getOutgoingQuote().getAttachments()); } + if (isDuplicate(message, threadId)) { + Log.w(TAG, "Ignoring duplicate media message (" + message.getSentTimeMillis() + ")"); + return -1; + } + long messageId = insertMediaMessage(message.getBody(), message.getAttachments(), quoteAttachments, message.getSharedContacts(), message.getLinkPreviews(), contentValues, insertListener); if (message.getRecipient().getAddress().isGroup()) { @@ -945,6 +950,19 @@ public class MmsDatabase extends MessagingDatabase { } } + private boolean isDuplicate(OutgoingMediaMessage message, long threadId) { + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + ADDRESS + " = ? AND " + THREAD_ID + " = ?", + new String[]{String.valueOf(message.getSentTimeMillis()), message.getRecipient().getAddress().serialize(), String.valueOf(threadId)}, + null, null, null, "1"); + + try { + return cursor != null && cursor.moveToFirst(); + } finally { + if (cursor != null) cursor.close(); + } + } + public boolean isSent(long messageId) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); try (Cursor cursor = database.query(TABLE_NAME, new String[] { MESSAGE_BOX }, ID + " = ?", new String[] { String.valueOf(messageId)}, null, null, null)) { 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 300a5c55f8..d5c34c5c54 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -456,7 +456,7 @@ public class SmsDatabase extends MessagingDatabase { contentValues.put(THREAD_ID, threadId); contentValues.put(BODY, message.getMessageBody()); contentValues.put(DATE_RECEIVED, System.currentTimeMillis()); - contentValues.put(DATE_SENT, date); + contentValues.put(DATE_SENT, message.getSentTimestampMillis()); contentValues.put(READ, 1); contentValues.put(TYPE, type); contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId()); @@ -464,6 +464,11 @@ public class SmsDatabase extends MessagingDatabase { 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()); + if (isDuplicate(message, threadId)) { + Log.w(TAG, "Duplicate message (" + message.getSentTimestampMillis() + "), ignoring..."); + return -1; + } + SQLiteDatabase db = databaseHelper.getWritableDatabase(); long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues); if (insertListener != null) { @@ -530,6 +535,19 @@ public class SmsDatabase extends MessagingDatabase { } } + private boolean isDuplicate(OutgoingTextMessage message, long threadId) { + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + ADDRESS + " = ? AND " + THREAD_ID + " = ?", + new String[]{String.valueOf(message.getSentTimestampMillis()), message.getRecipient().getAddress().serialize(), String.valueOf(threadId)}, + null, null, null, "1"); + + try { + return cursor != null && cursor.moveToFirst(); + } finally { + if (cursor != null) cursor.close(); + } + } + /*package */void deleteThread(long threadId) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.delete(TABLE_NAME, THREAD_ID + " = ?", new String[] {threadId+""}); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 6ab83939bf..ab54cc02e1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -471,7 +471,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } OutgoingTextMessage tm = new OutgoingTextMessage(Recipient.from(context, targetAddress, false), - body, message.getExpiresInSeconds(), -1); + body, message.getExpiresInSeconds(), -1, message.getTimestamp()); // Ignore the message if it has no body if (tm.getMessageBody().length() == 0) { return; } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java index 0d3cd92aa9..9ebd44891e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingTextMessage.java @@ -9,16 +9,18 @@ public class OutgoingTextMessage { private final String message; private final int subscriptionId; private final long expiresIn; + private final long sentTimestampMillis; - public OutgoingTextMessage(Recipient recipient, String message, long expiresIn, int subscriptionId) { + public OutgoingTextMessage(Recipient recipient, String message, long expiresIn, int subscriptionId, long sentTimestampMillis) { this.recipient = recipient; this.message = message; this.expiresIn = expiresIn; this.subscriptionId = subscriptionId; + this.sentTimestampMillis = sentTimestampMillis; } public static OutgoingTextMessage from(VisibleMessage message, Recipient recipient) { - return new OutgoingTextMessage(recipient, message.getText(), recipient.getExpireMessages() * 1000, -1); + return new OutgoingTextMessage(recipient, message.getText(), recipient.getExpireMessages() * 1000, -1, message.getSentTimestamp()); } public long getExpiresIn() { @@ -37,6 +39,10 @@ public class OutgoingTextMessage { return recipient; } + public long getSentTimestampMillis() { + return sentTimestampMillis; + } + public boolean isSecureMessage() { return true; } From 68506ffcfa68034dedea51126f93bb4f1e3b0b79 Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 1 Apr 2021 16:17:32 +1100 Subject: [PATCH 44/51] fix: don't store display name if current user's key --- .../messaging/sending_receiving/pollers/OpenGroupPoller.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index b4393046e0..f9b368ea4c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -201,6 +201,7 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ displayNameUpdates = setOf() OpenGroupAPI.getDisplayNames(hexEncodedPublicKeys, openGroup.server).successBackground { mapping -> for (pair in mapping.entries) { + if (pair.key == userHexEncodedPublicKey) continue val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})" MessagingConfiguration.shared.storage.setOpenGroupDisplayName(pair.key, openGroup.channel, openGroup.server, senderDisplayName) } From 7d9f5bc46d48fd4b867508b593b596e56dd9100c Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 1 Apr 2021 16:25:01 +1100 Subject: [PATCH 45/51] fix: replicate the same display name logic as settings activity --- .../messaging/sending_receiving/pollers/OpenGroupPoller.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index f9b368ea4c..d213b3b6a6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -202,7 +202,7 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ OpenGroupAPI.getDisplayNames(hexEncodedPublicKeys, openGroup.server).successBackground { mapping -> for (pair in mapping.entries) { if (pair.key == userHexEncodedPublicKey) continue - val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})" + val senderDisplayName = "${pair.value} (...${pair.key.substring(pair.key.count() - 8)})" MessagingConfiguration.shared.storage.setOpenGroupDisplayName(pair.key, openGroup.channel, openGroup.server, senderDisplayName) } }.fail { From 924f58f816b0a296820e90c5cb3f1da080c1402c Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 1 Apr 2021 16:39:37 +1100 Subject: [PATCH 46/51] fix: don't update user profile name if message handling open group --- .../sending_receiving/MessageReceiverHandler.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index 6e42ec23c3..bc2546b0a5 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -135,11 +135,13 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS val recipient = Recipient.from(context, Address.fromSerialized(message.sender!!), false) val displayName = newProfile.displayName!! val userPublicKey = storage.getUserPublicKey() - if (userPublicKey == message.sender) { - // Update the user's local name if the message came from their master device - TextSecurePreferences.setProfileName(context, displayName) + if (openGroupID == null) { + if (userPublicKey == message.sender) { + // Update the user's local name if the message came from their master device + TextSecurePreferences.setProfileName(context, displayName) + } + profileManager.setDisplayName(context, recipient, displayName) } - profileManager.setDisplayName(context, recipient, displayName) if (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, newProfile.profileKey)) { profileManager.setProfileKey(context, recipient, newProfile.profileKey!!) profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN) From 9ca6986b715fbd6bbc6f38889f47e60c8fef72cd Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 6 Apr 2021 16:05:59 +1000 Subject: [PATCH 47/51] fix sync of expiration timer update messages between linked devices --- .../conversation/ConversationActivity.java | 2 +- .../messages/control/ExpirationTimerUpdate.kt | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 6301ec6b56..3cceeb089c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -806,7 +806,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected Void doInBackground(Void... params) { DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime); - ExpirationTimerUpdate message = new ExpirationTimerUpdate(expirationTime); + ExpirationTimerUpdate message = new ExpirationTimerUpdate(null, expirationTime); message.setSentTimestamp(System.currentTimeMillis()); OutgoingExpirationUpdateMessage outgoingMessage = OutgoingExpirationUpdateMessage.from(message, recipient); try { diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt index 1473146237..85f34eed51 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ExpirationTimerUpdate.kt @@ -7,6 +7,8 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos class ExpirationTimerUpdate() : ControlMessage() { + /// In the case of a sync message, the public key of the person the message was targeted at. + /// - Note: `nil` if this isn't a sync message. var syncTarget: String? = null var duration: Int? = 0 @@ -19,13 +21,15 @@ class ExpirationTimerUpdate() : ControlMessage() { val dataMessageProto = if (proto.hasDataMessage()) proto.dataMessage else return null val isExpirationTimerUpdate = dataMessageProto.flags.and(SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE) != 0 if (!isExpirationTimerUpdate) return null + val syncTarget = dataMessageProto.syncTarget val duration = dataMessageProto.expireTimer - return ExpirationTimerUpdate(duration) + return ExpirationTimerUpdate(syncTarget, duration) } } //constructor - internal constructor(duration: Int) : this() { + internal constructor(syncTarget: String?, duration: Int) : this() { + this.syncTarget = syncTarget this.duration = duration } @@ -44,7 +48,10 @@ class ExpirationTimerUpdate() : ControlMessage() { val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder() dataMessageProto.flags = SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE dataMessageProto.expireTimer = duration - syncTarget?.let { dataMessageProto.syncTarget = it } + // Sync target + if (syncTarget != null) { + dataMessageProto.syncTarget = syncTarget + } // Group context if (MessagingConfiguration.shared.storage.isClosedGroup(recipient!!)) { try { From 3b2589b835a21ccd9ab44b243bd40f22ae682d04 Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 6 Apr 2021 17:09:21 +1000 Subject: [PATCH 48/51] refactor: use a shared timer in JobQueue.kt --- .../java/org/session/libsession/messaging/jobs/JobQueue.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index 2934e8dcd7..77eff071b0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -19,6 +19,7 @@ class JobQueue : JobDelegate { private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val scope = GlobalScope + SupervisorJob() private val queue = Channel(UNLIMITED) + val timer = Timer() init { // process jobs @@ -77,7 +78,7 @@ class JobQueue : JobDelegate { } else { val retryInterval = getRetryInterval(job) Log.i("Jobs", "${job::class.simpleName} failed; scheduling retry (failure count is ${job.failureCount}).") - Timer().schedule(delay = retryInterval) { + timer.schedule(delay = retryInterval) { Log.i("Jobs", "Retrying ${job::class.simpleName}.") queue.offer(job) } From 04f295ac6c7337731eed173f9873c12fe7d4a4c0 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Wed, 7 Apr 2021 10:40:45 +1000 Subject: [PATCH 49/51] expiration timer updates handled as outgoing messages for multi device --- .../securesms/database/MmsDatabase.java | 4 +- .../securesms/jobs/PushDecryptJob.java | 1 + .../service/ExpiringMessageManager.java | 49 ++++++++++++++----- .../OutgoingExpirationUpdateMessage.java | 2 +- .../signal/OutgoingGroupMediaMessage.java | 6 +-- .../messages/signal/OutgoingMediaMessage.java | 10 ++-- .../signal/OutgoingSecureMediaMessage.java | 3 +- 7 files changed, 53 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 3745735da5..62de9499a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -518,7 +518,9 @@ public class MmsDatabase extends MessagingDatabase { return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn); } - OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, distributionType, quote, contacts, previews, networkFailures, mismatches); + boolean expirationTimer = (outboxType & Types.EXPIRATION_TIMER_UPDATE_BIT) != 0; + + OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, expirationTimer, distributionType, quote, contacts, previews, networkFailures, mismatches); if (Types.isSecureType(outboxType)) { return new OutgoingSecureMediaMessage(message); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index ab54cc02e1..b0af4cd793 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -323,6 +323,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { attachments, message.getTimestamp(), -1, message.getExpiresInSeconds() * 1000, + false, DistributionTypes.DEFAULT, quote.orNull(), sharedContacts.or(Collections.emptyList()), linkPreviews.or(Collections.emptyList()), diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java index bd5b11cab2..2b614b4fd5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -5,10 +5,13 @@ import android.content.Context; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate; +import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage; import org.session.libsession.messaging.threads.Address; +import org.session.libsession.messaging.threads.DistributionTypes; import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.SSKEnvironment; +import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.service.api.messages.SignalServiceGroup; import org.session.libsignal.service.internal.push.SignalServiceProtos; @@ -23,6 +26,7 @@ import org.session.libsession.messaging.messages.signal.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.MmsException; import java.io.IOException; +import java.util.Collections; import java.util.Comparator; import java.util.TreeSet; import java.util.concurrent.Executor; @@ -71,6 +75,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM public void setExpirationTimer(@NotNull ExpirationTimerUpdate message) { MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + String userPublicKey = TextSecurePreferences.getLocalNumber(context); String senderPublicKey = message.getSender(); int duration = message.getDuration(); String groupPK = message.getGroupPublicKey(); @@ -85,23 +90,43 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM groupInfo = Optional.of(new SignalServiceGroup(GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL)); address = Address.fromSerialized(groupID); } else { - address = Address.fromSerialized(senderPublicKey); + address = Address.fromSerialized((message.getSyncTarget() != null && !message.getSyncTarget().isEmpty()) ? message.getSyncTarget() : senderPublicKey); } Recipient recipient = Recipient.from(context, address, false); if (recipient.isBlocked()) return; - IncomingMediaMessage mediaMessage = new IncomingMediaMessage(address, sentTimestamp, -1, - duration * 1000L, true, - false, - Optional.absent(), - groupInfo, - Optional.absent(), - Optional.absent(), - Optional.absent(), - Optional.absent()); - //insert the timer update message - database.insertSecureDecryptedMessageInbox(mediaMessage, -1); + // Notify the user + if (userPublicKey.equals(senderPublicKey)) { + // sender is a linked device + OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(recipient, + null, + Collections.emptyList(), + message.getSentTimestamp(), + -1, + duration * 1000L, + true, + DistributionTypes.DEFAULT, + null, + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList()); + database.insertSecureDecryptedMessageOutbox(mediaMessage, -1, sentTimestamp); + } else { + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(address, sentTimestamp, -1, + duration * 1000L, true, + false, + Optional.absent(), + groupInfo, + Optional.absent(), + Optional.absent(), + Optional.absent(), + Optional.absent()); + //insert the timer update message + database.insertSecureDecryptedMessageInbox(mediaMessage, -1); + } + //set the timer to the conversation DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, duration); diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java index 77996ba11e..74f72c4925 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingExpirationUpdateMessage.java @@ -12,7 +12,7 @@ public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn) { super(recipient, "", new LinkedList(), sentTimeMillis, - DistributionTypes.CONVERSATION, expiresIn, null, Collections.emptyList(), + DistributionTypes.CONVERSATION, expiresIn, true, null, Collections.emptyList(), Collections.emptyList()); } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java index 45f6531608..125aa26228 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingGroupMediaMessage.java @@ -32,7 +32,7 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { throws IOException { super(recipient, encodedGroupContext, avatar, sentTimeMillis, - DistributionTypes.CONVERSATION, expiresIn, quote, contacts, previews); + DistributionTypes.CONVERSATION, expiresIn, false, quote, contacts, previews); this.group = GroupContext.parseFrom(Base64.decode(encodedGroupContext)); } @@ -48,7 +48,7 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { super(recipient, Base64.encodeBytes(group.toByteArray()), new LinkedList() {{if (avatar != null) add(avatar);}}, System.currentTimeMillis(), - DistributionTypes.CONVERSATION, expireIn, quote, contacts, previews); + DistributionTypes.CONVERSATION, expireIn, false, quote, contacts, previews); this.group = group; } @@ -65,7 +65,7 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { super(recipient, Base64.encodeBytes(group.toByteArray()), new LinkedList() {{if (avatar != null) add(avatar);}}, sentTime, - DistributionTypes.CONVERSATION, expireIn, quote, contacts, previews); + DistributionTypes.CONVERSATION, expireIn, false, quote, contacts, previews); this.group = group; } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java index 8d818d956c..9a6618dc4a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java @@ -26,6 +26,7 @@ public class OutgoingMediaMessage { private final int distributionType; private final int subscriptionId; private final long expiresIn; + private final boolean expirationUpdate; private final QuoteModel outgoingQuote; private final List networkFailures = new LinkedList<>(); @@ -36,6 +37,7 @@ public class OutgoingMediaMessage { public OutgoingMediaMessage(Recipient recipient, String message, List attachments, long sentTimeMillis, int subscriptionId, long expiresIn, + boolean expirationUpdate, int distributionType, @Nullable QuoteModel outgoingQuote, @NonNull List contacts, @@ -50,6 +52,7 @@ public class OutgoingMediaMessage { this.attachments = attachments; this.subscriptionId = subscriptionId; this.expiresIn = expiresIn; + this.expirationUpdate = expirationUpdate; this.outgoingQuote = outgoingQuote; this.contacts.addAll(contacts); @@ -66,6 +69,7 @@ public class OutgoingMediaMessage { this.sentTimeMillis = that.sentTimeMillis; this.subscriptionId = that.subscriptionId; this.expiresIn = that.expiresIn; + this.expirationUpdate = that.expirationUpdate; this.outgoingQuote = that.outgoingQuote; this.identityKeyMismatches.addAll(that.identityKeyMismatches); @@ -85,7 +89,7 @@ public class OutgoingMediaMessage { previews = Collections.singletonList(linkPreview); } return new OutgoingMediaMessage(recipient, message.getText(), attachments, message.getSentTimestamp(), -1, - recipient.getExpireMessages() * 1000, DistributionTypes.DEFAULT, outgoingQuote, Collections.emptyList(), + recipient.getExpireMessages() * 1000, false, DistributionTypes.DEFAULT, outgoingQuote, Collections.emptyList(), previews, Collections.emptyList(), Collections.emptyList()); } @@ -109,9 +113,7 @@ public class OutgoingMediaMessage { return false; } - public boolean isExpirationUpdate() { - return false; - } + public boolean isExpirationUpdate() { return expirationUpdate; } public long getSentTimeMillis() { return sentTimeMillis; diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java index 8b5e7ddef0..c7822d8b90 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingSecureMediaMessage.java @@ -19,11 +19,12 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage { long sentTimeMillis, int distributionType, long expiresIn, + boolean expirationUpdate, @Nullable QuoteModel quote, @NonNull List contacts, @NonNull List previews) { - super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList()); + super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, expirationUpdate, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList()); } public OutgoingSecureMediaMessage(OutgoingMediaMessage base) { From b14988606821ab7f89ecd36b8e092a5b741dbc56 Mon Sep 17 00:00:00 2001 From: jubb Date: Wed, 7 Apr 2021 14:49:52 +1000 Subject: [PATCH 50/51] feat: add certificate pem files, set up network security config and point seed pools to port accordingly --- app/src/main/res/raw/lf_session_cert.pem | 24 ++++++++++++++++++ app/src/main/res/raw/seed1cert.pem | 25 +++++++++++++++++++ app/src/main/res/raw/seed3cert.pem | 25 +++++++++++++++++++ .../xml/network_security_configuration.xml | 18 +++++++++++++ .../org/session/libsession/snode/SnodeAPI.kt | 17 ++++++++----- .../libsignal/service/loki/api/SwarmAPI.kt | 14 ++++++++--- 6 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 app/src/main/res/raw/lf_session_cert.pem create mode 100644 app/src/main/res/raw/seed1cert.pem create mode 100644 app/src/main/res/raw/seed3cert.pem diff --git a/app/src/main/res/raw/lf_session_cert.pem b/app/src/main/res/raw/lf_session_cert.pem new file mode 100644 index 0000000000..344a055433 --- /dev/null +++ b/app/src/main/res/raw/lf_session_cert.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEEzCCAvugAwIBAgIUY9RQqbjhsQEkdeSgV9L0os9xZ7AwDQYJKoZIhvcNAQEL +BQAwfDELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN +ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x +HzAdBgNVBAMMFnB1YmxpYy5sb2tpLmZvdW5kYXRpb24wHhcNMjEwNDA3MDExMDMx +WhcNMjMwNDA3MDExMDMxWjB8MQswCQYDVQQGEwJBVTERMA8GA1UECAwIVmljdG9y +aWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2YWN5IFRl +Y2ggRm91bmRhdGlvbjEfMB0GA1UEAwwWcHVibGljLmxva2kuZm91bmRhdGlvbjCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM5dBJSIR5+VNNUxUOo6FG0e +RmZteRqBt50KXGbOi2A23a6sa57pLFh9Yw3hmlWV+QCL7ipG1X4IC55OStgoesf+ +K65VwEMP6Mtq0sSJS3R5TiuV2ZSRdSZTVjUyRXVe5T4Aw6wXVTAbc/HsyS780tDh +GclfDHhonPhZpmTAnSbfMOS+BfOnBNvDxdto0kVh6k5nrGlkT4ECloulHTQF2lwJ +0D6IOtv9AJplPdg6s2c4dY7durOdvr3NNVfvn5PTeRvbEPqzZur4WUUKIPNGu6mY +PxImqd4eUsL0Vod4aAsTIx4YMmCTi0m9W6zJI6nXcK/6a+iiA3+NTNMzEA9gQhEC +AwEAAaOBjDCBiTAdBgNVHQ4EFgQU/zahokxLvvFUpbnM6z/pwS1KsvwwHwYDVR0j +BBgwFoAU/zahokxLvvFUpbnM6z/pwS1KsvwwDwYDVR0TAQH/BAUwAwEB/zAhBgNV +HREEGjAYghZwdWJsaWMubG9raS5mb3VuZGF0aW9uMBMGA1UdJQQMMAoGCCsGAQUF +BwMBMA0GCSqGSIb3DQEBCwUAA4IBAQBql+JvoqpaYrFFTOuDn08U+pdcd3GM7tbI +zRH5LU+YnIpp9aRheek+2COW8DXsIy/kUngETCMLmX6ZaUj/WdHnTDkB0KTgxSHv +ad3ZznKPKZ26qJOklr+0ZWj4J3jHbisSzql6mqq7R2Kp4ESwzwqxvkbykM5RUnmz +Go/3Ol7bpN/ZVwwEkGfD/5rRHf57E/gZn2pBO+zotlQgr7HKRsIXQ2hIXVQqWmPQ +lvfIwrwAZlfES7BARFnHOpyVQxV8uNcV5K5eXzuVFjHBqvq+BtyGhWkP9yKJCHS9 +OUXxch0rzRsH2C/kRVVhEk0pI3qlFiRC8pCJs98SNE9l69EQtG7I +-----END CERTIFICATE----- diff --git a/app/src/main/res/raw/seed1cert.pem b/app/src/main/res/raw/seed1cert.pem new file mode 100644 index 0000000000..7360d6fca0 --- /dev/null +++ b/app/src/main/res/raw/seed1cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEITCCAwmgAwIBAgIUJsox1ZQPK/6iDsCC+MUJfNAlFuYwDQYJKoZIhvcNAQEL +BQAwgYAxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTESMBAGA1UEBwwJ +TWVsYm91cm5lMSUwIwYDVQQKDBxPeGVuIFByaXZhY3kgVGVjaCBGb3VuZGF0aW9u +MSMwIQYDVQQDDBpzdG9yYWdlLnNlZWQxLmxva2kubmV0d29yazAeFw0yMTA0MDcw +MTE5MjZaFw0yMzA0MDcwMTE5MjZaMIGAMQswCQYDVQQGEwJBVTERMA8GA1UECAwI +VmljdG9yaWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2 +YWN5IFRlY2ggRm91bmRhdGlvbjEjMCEGA1UEAwwac3RvcmFnZS5zZWVkMS5sb2tp +Lm5ldHdvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtWH3Rz8Dd +kEmM7tcBWHrJ/G8drr/+qidboEVYzxpyRjszaDxKXVhx4eBBsAD5RuCWuTuZmM8k +TKEDLtf8xfb5SQ7YNX+346s9NXS5Poy4CIPASiW/QWXgIHFbVdv2hC+cKOP61OLM +OGnOxfig6tQyd6EaCkedpY1DvSa2lPnQSOwC/jXCx6Vboc0zTY5R2bHtNc9hjIFP +F4VClLAQSh2F4R1V9MH5KZMW+CCP6oaJY658W9JYXYRwlLrL2EFOVxHgcxq/6+fw ++axXK9OXJrGZjuA+hiz+L/uAOtE4WuxrSeuNMHSrMtM9QqVn4bBuMJ21mAzfNoMP +OIwgMT9DwUjVAgMBAAGjgZAwgY0wHQYDVR0OBBYEFOubJp9SoXIw+ONiWgkOaW8K +zI/TMB8GA1UdIwQYMBaAFOubJp9SoXIw+ONiWgkOaW8KzI/TMA8GA1UdEwEB/wQF +MAMBAf8wJQYDVR0RBB4wHIIac3RvcmFnZS5zZWVkMS5sb2tpLm5ldHdvcmswEwYD +VR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAIiHNhNrjYvwXVWs +gacx8T/dpqpu9GE3L17LotgQr4R+IYHpNtcmwOTdtWWFfUTr75OCs+c3DqgRKEoj +lnULOsVcalpAGIvW15/fmZWOf66Dpa4+ljDmAc3SOQiD0gGNtqblgI5zG1HF38QP +hjYRhCZ5CVeGOLucvQ8tVVwQvArPFIkBr0jH9jHVgRWEI2MeI3FsU2H93D4TfGln +N4SmmCfYBqygaaZBWkJEt0bYhn8uGHdU9UY9L2FPtfHVKkmFgO7cASGlvXS7B/TT +/8IgbtM3O8mZc2asmdQhGwoAKz93ryyCd8X2UZJg/IwCSCayOlYZWY2fR4OPQmmV +gxJsm+g= +-----END CERTIFICATE----- diff --git a/app/src/main/res/raw/seed3cert.pem b/app/src/main/res/raw/seed3cert.pem new file mode 100644 index 0000000000..92574b769b --- /dev/null +++ b/app/src/main/res/raw/seed3cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEITCCAwmgAwIBAgIUc486Dy9Y00bUFfDeYmJIgSS5xREwDQYJKoZIhvcNAQEL +BQAwgYAxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTESMBAGA1UEBwwJ +TWVsYm91cm5lMSUwIwYDVQQKDBxPeGVuIFByaXZhY3kgVGVjaCBGb3VuZGF0aW9u +MSMwIQYDVQQDDBpzdG9yYWdlLnNlZWQzLmxva2kubmV0d29yazAeFw0yMTA0MDcw +MTIwNTJaFw0yMzA0MDcwMTIwNTJaMIGAMQswCQYDVQQGEwJBVTERMA8GA1UECAwI +VmljdG9yaWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2 +YWN5IFRlY2ggRm91bmRhdGlvbjEjMCEGA1UEAwwac3RvcmFnZS5zZWVkMy5sb2tp +Lm5ldHdvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtokMlsFzf +piYeD0EVNikMyvjltpF6fUEde9NOVrTtNTQT6kkDk+/0HF5LYgPaatv6v7fpUQHi +kIwd6F0LTRGeWDFdsaWMdtlR1n/GxLPrOROsE8dcLt6GLavPf9rDabgva93m/JD6 +XW+Ne+MPEwqS8dAmFGhZd0gju6AtKFoSHnIf5pSQN6fSZUF/JQtHLVprAKKWKDiS +ZwmWbmrZR2aofLD/VRpetabajnZlv9EeWloQwvUsw1C1hkAmmtFeeXtg7ePwrOzo +6CnmcUJwOmi+LWqQV4A+58RZPFKaZoC5pzaKd0OYB8eZ8HB1F41UjGJgheX5Cyl4 ++amfF3l8dSq1AgMBAAGjgZAwgY0wHQYDVR0OBBYEFM9VSq4pGydjtX92Beul4+ml +jBKtMB8GA1UdIwQYMBaAFM9VSq4pGydjtX92Beul4+mljBKtMA8GA1UdEwEB/wQF +MAMBAf8wJQYDVR0RBB4wHIIac3RvcmFnZS5zZWVkMy5sb2tpLm5ldHdvcmswEwYD +VR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAAYxmhhkcKE1n6g1 +JqOa3UCBo4EfbqY5+FDZ0FVqv/cwemwVpKLbe6luRIS8poomdPCyMOS45V7wN3H9 +cFpfJ1TW19ydPVKmCXrl29ngmnY1q7YDwE/4qi3VK/UiqDkTHMKWjVPkenOyi8u6 +VVQANXSnKrn6GtigNFjGyD38O+j7AUSXBtXOJczaoF6r6BWgwQZ2WmgjuwvKTWSN +4r8uObERoAQYVaeXfgdr4e9X/JdskBDaLFfoW/rrSozHB4FqVNFW96k+aIUgRa5p +9kv115QcBPCSh9qOyTHij4tswS6SyOFaiKrNC4hgHQXP4QgioKmtsR/2Y+qJ6ddH +6oo+4QU= +-----END CERTIFICATE----- diff --git a/app/src/main/res/xml/network_security_configuration.xml b/app/src/main/res/xml/network_security_configuration.xml index 85285232de..e0a3502bc1 100644 --- a/app/src/main/res/xml/network_security_configuration.xml +++ b/app/src/main/res/xml/network_security_configuration.xml @@ -3,4 +3,22 @@ 127.0.0.1 + + public.loki.foundation + + + + + + storage.seed1.loki.network + + + + + + storage.seed3.loki.network + + + + \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 98b711af60..9be9db1d29 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -2,21 +2,19 @@ package org.session.libsession.snode +import android.os.Build import nl.komponents.kovenant.* import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map - import org.session.libsession.snode.utilities.getRandomElement - -import org.session.libsignal.utilities.logging.Log -import org.session.libsignal.service.loki.api.utilities.HTTP import org.session.libsignal.service.loki.api.Snode +import org.session.libsignal.service.loki.api.utilities.HTTP import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol import org.session.libsignal.service.loki.utilities.Broadcaster import org.session.libsignal.service.loki.utilities.prettifiedDescription import org.session.libsignal.service.loki.utilities.retryIfNeeded import org.session.libsignal.utilities.* - +import org.session.libsignal.utilities.logging.Log import java.security.SecureRandom object SnodeAPI { @@ -36,7 +34,14 @@ object SnodeAPI { private val maxRetryCount = 6 private val minimumSnodePoolCount = 64 private val minimumSwarmSnodeCount = 2 - private val seedNodePool: Set = setOf( "https://storage.seed1.loki.network", "https://storage.seed3.loki.network", "https://public.loki.foundation" ) + + // use port 4433 if API level can handle network security config and enforce pinned certificates + private val seedPort = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) 443 else 4433 + private val seedNodePool: Set = setOf( + "https://storage.seed1.loki.network:$seedPort", + "https://storage.seed3.loki.network:$seedPort", + "https://public.loki.foundation:$seedPort" + ) internal val snodeFailureThreshold = 4 private val targetSwarmSnodeCount = 2 diff --git a/libsignal/src/main/java/org/session/libsignal/service/loki/api/SwarmAPI.kt b/libsignal/src/main/java/org/session/libsignal/service/loki/api/SwarmAPI.kt index f42f772c1e..26dbf698e6 100644 --- a/libsignal/src/main/java/org/session/libsignal/service/loki/api/SwarmAPI.kt +++ b/libsignal/src/main/java/org/session/libsignal/service/loki/api/SwarmAPI.kt @@ -1,17 +1,18 @@ package org.session.libsignal.service.loki.api +import android.os.Build import nl.komponents.kovenant.Promise import nl.komponents.kovenant.deferred import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.task -import org.session.libsignal.utilities.logging.Log import org.session.libsignal.service.loki.api.utilities.HTTP import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol -import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.service.loki.utilities.getRandomElement import org.session.libsignal.service.loki.utilities.prettifiedDescription import org.session.libsignal.service.loki.utilities.retryIfNeeded +import org.session.libsignal.utilities.ThreadUtils +import org.session.libsignal.utilities.logging.Log import java.security.SecureRandom import java.util.* @@ -23,7 +24,14 @@ class SwarmAPI private constructor(private val database: LokiAPIDatabaseProtocol set(newValue) { database.setSnodePool(newValue) } companion object { - private val seedNodePool: Set = setOf( "https://storage.seed1.loki.network", "https://storage.seed3.loki.network", "https://public.loki.foundation" ) + + // use port 4433 if API level can handle network security config and enforce pinned certificates + private val seedPort = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) 443 else 4433 + private val seedNodePool: Set = setOf( + "https://storage.seed1.loki.network:$seedPort", + "https://storage.seed3.loki.network:$seedPort", + "https://public.loki.foundation:$seedPort" + ) // region Settings private val minimumSnodePoolCount = 64 From 5debd8f7396914b13373d52e4a482afad0d37f27 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 7 Apr 2021 15:14:59 +1000 Subject: [PATCH 51/51] Update version number --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 45d5a18a68..75fe0bc590 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -157,7 +157,7 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 147 +def canonicalVersionCode = 150 def canonicalVersionName = "1.9.0" def postFixSize = 10