From c0a5a6143154c810ce236ed3c0213a544c15915d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 10 Aug 2021 09:36:20 +1000 Subject: [PATCH 01/43] add unsend request proto --- .../messages/control/UnsendRequest.kt | 55 + libsignal/protobuf/SignalService.proto | 8 + .../libsignal/protos/SignalServiceProtos.java | 1047 +++++++++++++++-- 3 files changed, 1019 insertions(+), 91 deletions(-) create mode 100644 libsession/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt new file mode 100644 index 000000000..a22dd9334 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt @@ -0,0 +1,55 @@ +package org.session.libsession.messaging.messages.control + +import org.session.libsignal.protos.SignalServiceProtos +import org.session.libsignal.utilities.Log + +class UnsendRequest(): ControlMessage() { + var timestamp: Long? = null + var author: String? = null + + override val isSelfSendValid: Boolean = true + + // region Validation + override fun isValid(): Boolean { + if (!super.isValid()) return false + return timestamp != null && author != null + } + // endregion + + companion object { + const val TAG = "UnsendRequest" + + fun fromProto(proto: SignalServiceProtos.Content): UnsendRequest? { + val unsendRequestProto = if (proto.hasUnsendRequest()) proto.unsendRequest else return null + val timestamp = unsendRequestProto.timestamp + val author = unsendRequestProto.author + return UnsendRequest(timestamp, author) + } + } + + constructor(timestamp: Long, author: String) : this() { + this.timestamp = timestamp + this.author = author + } + + override fun toProto(): SignalServiceProtos.Content? { + val timestamp = timestamp + val author = author + if (timestamp == null || author == null) { + Log.w(TAG, "Couldn't construct unsend request proto from: $this") + return null + } + val unsendRequestProto = SignalServiceProtos.UnsendRequest.newBuilder() + unsendRequestProto.timestamp = timestamp + unsendRequestProto.author = author + val contentProto = SignalServiceProtos.Content.newBuilder() + try { + contentProto.unsendRequest = unsendRequestProto.build() + return contentProto.build() + } catch (e: Exception) { + Log.w(TAG, "Couldn't construct unsend request proto from: $this") + return null + } + } + +} \ No newline at end of file diff --git a/libsignal/protobuf/SignalService.proto b/libsignal/protobuf/SignalService.proto index 7ee8dc994..390a67a1a 100644 --- a/libsignal/protobuf/SignalService.proto +++ b/libsignal/protobuf/SignalService.proto @@ -35,12 +35,20 @@ message TypingMessage { required Action action = 2; } +message UnsendRequest { + // @required + required uint64 timestamp = 1; + // @required + required string author = 2; +} + message Content { optional DataMessage dataMessage = 1; optional ReceiptMessage receiptMessage = 5; optional TypingMessage typingMessage = 6; optional ConfigurationMessage configurationMessage = 7; optional DataExtractionNotification dataExtractionNotification = 8; + optional UnsendRequest unsendRequest = 9; } message KeyPair { diff --git a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java index 0ca46d3a9..4dce0ba51 100644 --- a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java +++ b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java @@ -1706,6 +1706,654 @@ public final class SignalServiceProtos { // @@protoc_insertion_point(class_scope:signalservice.TypingMessage) } + public interface UnsendRequestOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // required uint64 timestamp = 1; + /** + * required uint64 timestamp = 1; + * + *
+     * @required
+     * 
+ */ + boolean hasTimestamp(); + /** + * required uint64 timestamp = 1; + * + *
+     * @required
+     * 
+ */ + long getTimestamp(); + + // required string author = 2; + /** + * required string author = 2; + * + *
+     * @required
+     * 
+ */ + boolean hasAuthor(); + /** + * required string author = 2; + * + *
+     * @required
+     * 
+ */ + java.lang.String getAuthor(); + /** + * required string author = 2; + * + *
+     * @required
+     * 
+ */ + com.google.protobuf.ByteString + getAuthorBytes(); + } + /** + * Protobuf type {@code signalservice.UnsendRequest} + */ + public static final class UnsendRequest extends + com.google.protobuf.GeneratedMessage + implements UnsendRequestOrBuilder { + // Use UnsendRequest.newBuilder() to construct. + private UnsendRequest(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private UnsendRequest(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final UnsendRequest defaultInstance; + public static UnsendRequest getDefaultInstance() { + return defaultInstance; + } + + public UnsendRequest getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private UnsendRequest( + 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: { + bitField0_ |= 0x00000001; + timestamp_ = input.readUInt64(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + author_ = input.readBytes(); + 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.protos.SignalServiceProtos.internal_static_signalservice_UnsendRequest_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_UnsendRequest_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.class, org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public UnsendRequest parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new UnsendRequest(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // required uint64 timestamp = 1; + public static final int TIMESTAMP_FIELD_NUMBER = 1; + private long timestamp_; + /** + * required uint64 timestamp = 1; + * + *
+     * @required
+     * 
+ */ + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required uint64 timestamp = 1; + * + *
+     * @required
+     * 
+ */ + public long getTimestamp() { + return timestamp_; + } + + // required string author = 2; + public static final int AUTHOR_FIELD_NUMBER = 2; + private java.lang.Object author_; + /** + * required string author = 2; + * + *
+     * @required
+     * 
+ */ + public boolean hasAuthor() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required string author = 2; + * + *
+     * @required
+     * 
+ */ + public java.lang.String getAuthor() { + java.lang.Object ref = author_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + author_ = s; + } + return s; + } + } + /** + * required string author = 2; + * + *
+     * @required
+     * 
+ */ + public com.google.protobuf.ByteString + getAuthorBytes() { + java.lang.Object ref = author_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + author_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private void initFields() { + timestamp_ = 0L; + author_ = ""; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (!hasTimestamp()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasAuthor()) { + 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.writeUInt64(1, timestamp_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, getAuthorBytes()); + } + 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 + .computeUInt64Size(1, timestamp_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, getAuthorBytes()); + } + 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.protos.SignalServiceProtos.UnsendRequest parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest 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.protos.SignalServiceProtos.UnsendRequest parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest 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.protos.SignalServiceProtos.UnsendRequest 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.UnsendRequest} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_UnsendRequest_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_UnsendRequest_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.class, org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder.class); + } + + // Construct using org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.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(); + timestamp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000001); + author_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_UnsendRequest_descriptor; + } + + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequest getDefaultInstanceForType() { + return org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance(); + } + + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequest build() { + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequest buildPartial() { + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest result = new org.session.libsignal.protos.SignalServiceProtos.UnsendRequest(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.timestamp_ = timestamp_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.author_ = author_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.session.libsignal.protos.SignalServiceProtos.UnsendRequest) { + return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.UnsendRequest)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.UnsendRequest other) { + if (other == org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance()) return this; + if (other.hasTimestamp()) { + setTimestamp(other.getTimestamp()); + } + if (other.hasAuthor()) { + bitField0_ |= 0x00000002; + author_ = other.author_; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (!hasTimestamp()) { + + return false; + } + if (!hasAuthor()) { + + return false; + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.UnsendRequest) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // required uint64 timestamp = 1; + private long timestamp_ ; + /** + * required uint64 timestamp = 1; + * + *
+       * @required
+       * 
+ */ + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required uint64 timestamp = 1; + * + *
+       * @required
+       * 
+ */ + public long getTimestamp() { + return timestamp_; + } + /** + * required uint64 timestamp = 1; + * + *
+       * @required
+       * 
+ */ + public Builder setTimestamp(long value) { + bitField0_ |= 0x00000001; + timestamp_ = value; + onChanged(); + return this; + } + /** + * required uint64 timestamp = 1; + * + *
+       * @required
+       * 
+ */ + public Builder clearTimestamp() { + bitField0_ = (bitField0_ & ~0x00000001); + timestamp_ = 0L; + onChanged(); + return this; + } + + // required string author = 2; + private java.lang.Object author_ = ""; + /** + * required string author = 2; + * + *
+       * @required
+       * 
+ */ + public boolean hasAuthor() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required string author = 2; + * + *
+       * @required
+       * 
+ */ + public java.lang.String getAuthor() { + java.lang.Object ref = author_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + author_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * required string author = 2; + * + *
+       * @required
+       * 
+ */ + public com.google.protobuf.ByteString + getAuthorBytes() { + java.lang.Object ref = author_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + author_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * required string author = 2; + * + *
+       * @required
+       * 
+ */ + public Builder setAuthor( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + author_ = value; + onChanged(); + return this; + } + /** + * required string author = 2; + * + *
+       * @required
+       * 
+ */ + public Builder clearAuthor() { + bitField0_ = (bitField0_ & ~0x00000002); + author_ = getDefaultInstance().getAuthor(); + onChanged(); + return this; + } + /** + * required string author = 2; + * + *
+       * @required
+       * 
+ */ + public Builder setAuthorBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + author_ = value; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:signalservice.UnsendRequest) + } + + static { + defaultInstance = new UnsendRequest(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:signalservice.UnsendRequest) + } + public interface ContentOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -1778,6 +2426,20 @@ public final class SignalServiceProtos { * optional .signalservice.DataExtractionNotification dataExtractionNotification = 8; */ org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotificationOrBuilder getDataExtractionNotificationOrBuilder(); + + // optional .signalservice.UnsendRequest unsendRequest = 9; + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + boolean hasUnsendRequest(); + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest getUnsendRequest(); + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder getUnsendRequestOrBuilder(); } /** * Protobuf type {@code signalservice.Content} @@ -1895,6 +2557,19 @@ public final class SignalServiceProtos { bitField0_ |= 0x00000010; break; } + case 74: { + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder subBuilder = null; + if (((bitField0_ & 0x00000020) == 0x00000020)) { + subBuilder = unsendRequest_.toBuilder(); + } + unsendRequest_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(unsendRequest_); + unsendRequest_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000020; + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -2045,12 +2720,35 @@ public final class SignalServiceProtos { return dataExtractionNotification_; } + // optional .signalservice.UnsendRequest unsendRequest = 9; + public static final int UNSENDREQUEST_FIELD_NUMBER = 9; + private org.session.libsignal.protos.SignalServiceProtos.UnsendRequest unsendRequest_; + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public boolean hasUnsendRequest() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequest getUnsendRequest() { + return unsendRequest_; + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder getUnsendRequestOrBuilder() { + return unsendRequest_; + } + private void initFields() { dataMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.getDefaultInstance(); receiptMessage_ = org.session.libsignal.protos.SignalServiceProtos.ReceiptMessage.getDefaultInstance(); typingMessage_ = org.session.libsignal.protos.SignalServiceProtos.TypingMessage.getDefaultInstance(); configurationMessage_ = org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.getDefaultInstance(); dataExtractionNotification_ = org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotification.getDefaultInstance(); + unsendRequest_ = org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance(); } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -2087,6 +2785,12 @@ public final class SignalServiceProtos { return false; } } + if (hasUnsendRequest()) { + if (!getUnsendRequest().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } memoizedIsInitialized = 1; return true; } @@ -2109,6 +2813,9 @@ public final class SignalServiceProtos { if (((bitField0_ & 0x00000010) == 0x00000010)) { output.writeMessage(8, dataExtractionNotification_); } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeMessage(9, unsendRequest_); + } getUnknownFields().writeTo(output); } @@ -2138,6 +2845,10 @@ public final class SignalServiceProtos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(8, dataExtractionNotification_); } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(9, unsendRequest_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -2251,6 +2962,7 @@ public final class SignalServiceProtos { getTypingMessageFieldBuilder(); getConfigurationMessageFieldBuilder(); getDataExtractionNotificationFieldBuilder(); + getUnsendRequestFieldBuilder(); } } private static Builder create() { @@ -2289,6 +3001,12 @@ public final class SignalServiceProtos { dataExtractionNotificationBuilder_.clear(); } bitField0_ = (bitField0_ & ~0x00000010); + if (unsendRequestBuilder_ == null) { + unsendRequest_ = org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance(); + } else { + unsendRequestBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000020); return this; } @@ -2357,6 +3075,14 @@ public final class SignalServiceProtos { } else { result.dataExtractionNotification_ = dataExtractionNotificationBuilder_.build(); } + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000020; + } + if (unsendRequestBuilder_ == null) { + result.unsendRequest_ = unsendRequest_; + } else { + result.unsendRequest_ = unsendRequestBuilder_.build(); + } result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -2388,6 +3114,9 @@ public final class SignalServiceProtos { if (other.hasDataExtractionNotification()) { mergeDataExtractionNotification(other.getDataExtractionNotification()); } + if (other.hasUnsendRequest()) { + mergeUnsendRequest(other.getUnsendRequest()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -2423,6 +3152,12 @@ public final class SignalServiceProtos { return false; } } + if (hasUnsendRequest()) { + if (!getUnsendRequest().isInitialized()) { + + return false; + } + } return true; } @@ -3030,6 +3765,123 @@ public final class SignalServiceProtos { return dataExtractionNotificationBuilder_; } + // optional .signalservice.UnsendRequest unsendRequest = 9; + private org.session.libsignal.protos.SignalServiceProtos.UnsendRequest unsendRequest_ = org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest, org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder, org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder> unsendRequestBuilder_; + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public boolean hasUnsendRequest() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequest getUnsendRequest() { + if (unsendRequestBuilder_ == null) { + return unsendRequest_; + } else { + return unsendRequestBuilder_.getMessage(); + } + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public Builder setUnsendRequest(org.session.libsignal.protos.SignalServiceProtos.UnsendRequest value) { + if (unsendRequestBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + unsendRequest_ = value; + onChanged(); + } else { + unsendRequestBuilder_.setMessage(value); + } + bitField0_ |= 0x00000020; + return this; + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public Builder setUnsendRequest( + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder builderForValue) { + if (unsendRequestBuilder_ == null) { + unsendRequest_ = builderForValue.build(); + onChanged(); + } else { + unsendRequestBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000020; + return this; + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public Builder mergeUnsendRequest(org.session.libsignal.protos.SignalServiceProtos.UnsendRequest value) { + if (unsendRequestBuilder_ == null) { + if (((bitField0_ & 0x00000020) == 0x00000020) && + unsendRequest_ != org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance()) { + unsendRequest_ = + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.newBuilder(unsendRequest_).mergeFrom(value).buildPartial(); + } else { + unsendRequest_ = value; + } + onChanged(); + } else { + unsendRequestBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000020; + return this; + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public Builder clearUnsendRequest() { + if (unsendRequestBuilder_ == null) { + unsendRequest_ = org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance(); + onChanged(); + } else { + unsendRequestBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000020); + return this; + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder getUnsendRequestBuilder() { + bitField0_ |= 0x00000020; + onChanged(); + return getUnsendRequestFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder getUnsendRequestOrBuilder() { + if (unsendRequestBuilder_ != null) { + return unsendRequestBuilder_.getMessageOrBuilder(); + } else { + return unsendRequest_; + } + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest, org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder, org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder> + getUnsendRequestFieldBuilder() { + if (unsendRequestBuilder_ == null) { + unsendRequestBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest, org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder, org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder>( + unsendRequest_, + getParentForChildren(), + isClean()); + unsendRequest_ = null; + } + return unsendRequestBuilder_; + } + // @@protoc_insertion_point(builder_scope:signalservice.Content) } @@ -21254,6 +22106,11 @@ public final class SignalServiceProtos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_TypingMessage_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_signalservice_UnsendRequest_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_signalservice_UnsendRequest_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_Content_descriptor; private static @@ -21357,88 +22214,90 @@ public final class SignalServiceProtos { "SSAGE\020\007\"{\n\rTypingMessage\022\021\n\ttimestamp\030\001 " + "\002(\004\0223\n\006action\030\002 \002(\0162#.signalservice.Typi" + "ngMessage.Action\"\"\n\006Action\022\013\n\007STARTED\020\000\022" + - "\013\n\007STOPPED\020\001\"\270\002\n\007Content\022/\n\013dataMessage\030", - "\001 \001(\0132\032.signalservice.DataMessage\0225\n\016rec" + - "eiptMessage\030\005 \001(\0132\035.signalservice.Receip" + - "tMessage\0223\n\rtypingMessage\030\006 \001(\0132\034.signal" + - "service.TypingMessage\022A\n\024configurationMe" + - "ssage\030\007 \001(\0132#.signalservice.Configuratio" + - "nMessage\022M\n\032dataExtractionNotification\030\010" + - " \001(\0132).signalservice.DataExtractionNotif" + - "ication\"0\n\007KeyPair\022\021\n\tpublicKey\030\001 \002(\014\022\022\n" + - "\nprivateKey\030\002 \002(\014\"\226\001\n\032DataExtractionNoti" + - "fication\022<\n\004type\030\001 \002(\0162..signalservice.D", - "ataExtractionNotification.Type\022\021\n\ttimest" + - "amp\030\002 \001(\004\"\'\n\004Type\022\016\n\nSCREENSHOT\020\001\022\017\n\013MED" + - "IA_SAVED\020\002\"\245\014\n\013DataMessage\022\014\n\004body\030\001 \001(\t" + - "\0225\n\013attachments\030\002 \003(\0132 .signalservice.At" + - "tachmentPointer\022*\n\005group\030\003 \001(\0132\033.signals" + - "ervice.GroupContext\022\r\n\005flags\030\004 \001(\r\022\023\n\013ex" + - "pireTimer\030\005 \001(\r\022\022\n\nprofileKey\030\006 \001(\014\022\021\n\tt" + - "imestamp\030\007 \001(\004\022/\n\005quote\030\010 \001(\0132 .signalse" + - "rvice.DataMessage.Quote\0223\n\007preview\030\n \003(\013" + - "2\".signalservice.DataMessage.Preview\0227\n\007", - "profile\030e \001(\0132&.signalservice.DataMessag" + - "e.LokiProfile\022K\n\023openGroupInvitation\030f \001" + - "(\0132..signalservice.DataMessage.OpenGroup" + - "Invitation\022W\n\031closedGroupControlMessage\030" + - "h \001(\01324.signalservice.DataMessage.Closed" + - "GroupControlMessage\022\022\n\nsyncTarget\030i \001(\t\032" + - "\225\002\n\005Quote\022\n\n\002id\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\022\014\n" + - "\004text\030\003 \001(\t\022F\n\013attachments\030\004 \003(\01321.signa" + - "lservice.DataMessage.Quote.QuotedAttachm" + - "ent\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.AttachmentPointer\022\r\n\005" + - "flags\030\004 \001(\r\"\032\n\005Flags\022\021\n\rVOICE_MESSAGE\020\001\032" + - "V\n\007Preview\022\013\n\003url\030\001 \002(\t\022\r\n\005title\030\002 \001(\t\022/" + - "\n\005image\030\003 \001(\0132 .signalservice.Attachment" + - "Pointer\032:\n\013LokiProfile\022\023\n\013displayName\030\001 " + - "\001(\t\022\026\n\016profilePicture\030\002 \001(\t\0320\n\023OpenGroup" + - "Invitation\022\013\n\003url\030\001 \002(\t\022\014\n\004name\030\003 \002(\t\032\374\003" + - "\n\031ClosedGroupControlMessage\022G\n\004type\030\001 \002(" + - "\01629.signalservice.DataMessage.ClosedGrou", - "pControlMessage.Type\022\021\n\tpublicKey\030\002 \001(\014\022" + - "\014\n\004name\030\003 \001(\t\0221\n\021encryptionKeyPair\030\004 \001(\013" + - "2\026.signalservice.KeyPair\022\017\n\007members\030\005 \003(" + - "\014\022\016\n\006admins\030\006 \003(\014\022U\n\010wrappers\030\007 \003(\0132C.si" + - "gnalservice.DataMessage.ClosedGroupContr" + - "olMessage.KeyPairWrapper\022\027\n\017expirationTi" + - "mer\030\010 \001(\r\032=\n\016KeyPairWrapper\022\021\n\tpublicKey" + - "\030\001 \002(\014\022\030\n\020encryptedKeyPair\030\002 \002(\014\"r\n\004Type" + - "\022\007\n\003NEW\020\001\022\027\n\023ENCRYPTION_KEY_PAIR\020\003\022\017\n\013NA" + - "ME_CHANGE\020\004\022\021\n\rMEMBERS_ADDED\020\005\022\023\n\017MEMBER", - "S_REMOVED\020\006\022\017\n\013MEMBER_LEFT\020\007\"$\n\005Flags\022\033\n" + - "\027EXPIRATION_TIMER_UPDATE\020\002\"\347\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\233\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\022\027\n\017expirationTimer\030\006 \001(\r\032V\n\007Contact\022" + - "\021\n\tpublicKey\030\001 \002(\014\022\014\n\004name\030\002 \002(\t\022\026\n\016prof" + - "ilePicture\030\003 \001(\t\022\022\n\nprofileKey\030\004 \001(\014\"u\n\016" + - "ReceiptMessage\0220\n\004type\030\001 \002(\0162\".signalser" + - "vice.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\021" + - "AttachmentPointer\022\n\n\002id\030\001 \002(\006\022\023\n\013content" + - "Type\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\010file", - "Name\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\007caption\030\013 \001(\t\022\013\n\003url" + - "\030e \001(\t\"\032\n\005Flags\022\021\n\rVOICE_MESSAGE\020\001\"\365\001\n\014G" + - "roupContext\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\006admi" + - "ns\030\006 \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\004B3\n\034org.session.libsignal.protosB\023Sign", - "alServiceProtos" + "\013\n\007STOPPED\020\001\"2\n\rUnsendRequest\022\021\n\ttimesta", + "mp\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\"\355\002\n\007Content\022/\n\013" + + "dataMessage\030\001 \001(\0132\032.signalservice.DataMe" + + "ssage\0225\n\016receiptMessage\030\005 \001(\0132\035.signalse" + + "rvice.ReceiptMessage\0223\n\rtypingMessage\030\006 " + + "\001(\0132\034.signalservice.TypingMessage\022A\n\024con" + + "figurationMessage\030\007 \001(\0132#.signalservice." + + "ConfigurationMessage\022M\n\032dataExtractionNo" + + "tification\030\010 \001(\0132).signalservice.DataExt" + + "ractionNotification\0223\n\runsendRequest\030\t \001" + + "(\0132\034.signalservice.UnsendRequest\"0\n\007KeyP", + "air\022\021\n\tpublicKey\030\001 \002(\014\022\022\n\nprivateKey\030\002 \002" + + "(\014\"\226\001\n\032DataExtractionNotification\022<\n\004typ" + + "e\030\001 \002(\0162..signalservice.DataExtractionNo" + + "tification.Type\022\021\n\ttimestamp\030\002 \001(\004\"\'\n\004Ty" + + "pe\022\016\n\nSCREENSHOT\020\001\022\017\n\013MEDIA_SAVED\020\002\"\245\014\n\013" + + "DataMessage\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.GroupCon" + + "text\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\005quote\030\010 \001(\0132 .signalservice.DataMessa" + + "ge.Quote\0223\n\007preview\030\n \003(\0132\".signalservic" + + "e.DataMessage.Preview\0227\n\007profile\030e \001(\0132&" + + ".signalservice.DataMessage.LokiProfile\022K" + + "\n\023openGroupInvitation\030f \001(\0132..signalserv" + + "ice.DataMessage.OpenGroupInvitation\022W\n\031c" + + "losedGroupControlMessage\030h \001(\01324.signals" + + "ervice.DataMessage.ClosedGroupControlMes" + + "sage\022\022\n\nsyncTarget\030i \001(\t\032\225\002\n\005Quote\022\n\n\002id" + + "\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\022\014\n\004text\030\003 \001(\t\022F\n\013", + "attachments\030\004 \003(\01321.signalservice.DataMe" + + "ssage.Quote.QuotedAttachment\032\231\001\n\020QuotedA" + + "ttachment\022\023\n\013contentType\030\001 \001(\t\022\020\n\010fileNa" + + "me\030\002 \001(\t\0223\n\tthumbnail\030\003 \001(\0132 .signalserv" + + "ice.AttachmentPointer\022\r\n\005flags\030\004 \001(\r\"\032\n\005" + + "Flags\022\021\n\rVOICE_MESSAGE\020\001\032V\n\007Preview\022\013\n\003u" + + "rl\030\001 \002(\t\022\r\n\005title\030\002 \001(\t\022/\n\005image\030\003 \001(\0132 " + + ".signalservice.AttachmentPointer\032:\n\013Loki" + + "Profile\022\023\n\013displayName\030\001 \001(\t\022\026\n\016profileP" + + "icture\030\002 \001(\t\0320\n\023OpenGroupInvitation\022\013\n\003u", + "rl\030\001 \002(\t\022\014\n\004name\030\003 \002(\t\032\374\003\n\031ClosedGroupCo" + + "ntrolMessage\022G\n\004type\030\001 \002(\01629.signalservi" + + "ce.DataMessage.ClosedGroupControlMessage" + + ".Type\022\021\n\tpublicKey\030\002 \001(\014\022\014\n\004name\030\003 \001(\t\0221" + + "\n\021encryptionKeyPair\030\004 \001(\0132\026.signalservic" + + "e.KeyPair\022\017\n\007members\030\005 \003(\014\022\016\n\006admins\030\006 \003" + + "(\014\022U\n\010wrappers\030\007 \003(\0132C.signalservice.Dat" + + "aMessage.ClosedGroupControlMessage.KeyPa" + + "irWrapper\022\027\n\017expirationTimer\030\010 \001(\r\032=\n\016Ke" + + "yPairWrapper\022\021\n\tpublicKey\030\001 \002(\014\022\030\n\020encry", + "ptedKeyPair\030\002 \002(\014\"r\n\004Type\022\007\n\003NEW\020\001\022\027\n\023EN" + + "CRYPTION_KEY_PAIR\020\003\022\017\n\013NAME_CHANGE\020\004\022\021\n\r" + + "MEMBERS_ADDED\020\005\022\023\n\017MEMBERS_REMOVED\020\006\022\017\n\013" + + "MEMBER_LEFT\020\007\"$\n\005Flags\022\033\n\027EXPIRATION_TIM" + + "ER_UPDATE\020\002\"\347\003\n\024ConfigurationMessage\022E\n\014" + + "closedGroups\030\001 \003(\0132/.signalservice.Confi" + + "gurationMessage.ClosedGroup\022\022\n\nopenGroup" + + "s\030\002 \003(\t\022\023\n\013displayName\030\003 \001(\t\022\026\n\016profileP" + + "icture\030\004 \001(\t\022\022\n\nprofileKey\030\005 \001(\014\022=\n\010cont" + + "acts\030\006 \003(\0132+.signalservice.Configuration", + "Message.Contact\032\233\001\n\013ClosedGroup\022\021\n\tpubli" + + "cKey\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\0221\n\021encryptionKe" + + "yPair\030\003 \001(\0132\026.signalservice.KeyPair\022\017\n\007m" + + "embers\030\004 \003(\014\022\016\n\006admins\030\005 \003(\014\022\027\n\017expirati" + + "onTimer\030\006 \001(\r\032V\n\007Contact\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\022" + + "0\n\004type\030\001 \002(\0162\".signalservice.ReceiptMes" + + "sage.Type\022\021\n\ttimestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010" + + "DELIVERY\020\000\022\010\n\004READ\020\001\"\354\001\n\021AttachmentPoint", + "er\022\n\n\002id\030\001 \002(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003k" + + "ey\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\005f" + + "lags\030\010 \001(\r\022\r\n\005width\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\"\365\001\n\014GroupContext\022\n\n\002" + + "id\030\001 \001(\014\022.\n\004type\030\002 \001(\0162 .signalservice.G" + + "roupContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007member" + + "s\030\004 \003(\t\0220\n\006avatar\030\005 \001(\0132 .signalservice." + + "AttachmentPointer\022\016\n\006admins\030\006 \003(\t\"H\n\004Typ", + "e\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\004B3\n\034org.sessi" + + "on.libsignal.protosB\023SignalServiceProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -21457,26 +22316,32 @@ public final class SignalServiceProtos { com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_TypingMessage_descriptor, new java.lang.String[] { "Timestamp", "Action", }); - internal_static_signalservice_Content_descriptor = + internal_static_signalservice_UnsendRequest_descriptor = getDescriptor().getMessageTypes().get(2); + internal_static_signalservice_UnsendRequest_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_signalservice_UnsendRequest_descriptor, + new java.lang.String[] { "Timestamp", "Author", }); + internal_static_signalservice_Content_descriptor = + getDescriptor().getMessageTypes().get(3); internal_static_signalservice_Content_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_Content_descriptor, - new java.lang.String[] { "DataMessage", "ReceiptMessage", "TypingMessage", "ConfigurationMessage", "DataExtractionNotification", }); + new java.lang.String[] { "DataMessage", "ReceiptMessage", "TypingMessage", "ConfigurationMessage", "DataExtractionNotification", "UnsendRequest", }); internal_static_signalservice_KeyPair_descriptor = - getDescriptor().getMessageTypes().get(3); + getDescriptor().getMessageTypes().get(4); internal_static_signalservice_KeyPair_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_KeyPair_descriptor, new java.lang.String[] { "PublicKey", "PrivateKey", }); internal_static_signalservice_DataExtractionNotification_descriptor = - getDescriptor().getMessageTypes().get(4); + 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(5); + getDescriptor().getMessageTypes().get(6); internal_static_signalservice_DataMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_descriptor, @@ -21524,7 +22389,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, @@ -21542,19 +22407,19 @@ 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, From 2cb91c3a0e5b4a2faae8b338446a77f8a846a9fd Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 10 Aug 2021 12:52:01 +1000 Subject: [PATCH 02/43] add delete endpoint call in snode api --- .../org/session/libsession/snode/SnodeAPI.kt | 44 +++++++++++++++++++ .../org/session/libsignal/utilities/Snode.kt | 1 + 2 files changed, 45 insertions(+) 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 4da3409d0..d297ba588 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -329,6 +329,50 @@ object SnodeAPI { } } + fun deleteMessage(publicKey: String, serverHashes: List): Promise, Exception> { + return retryIfNeeded(maxRetryCount) { + val module = MessagingModuleConfiguration.shared + val userED25519KeyPair = module.getUserED25519KeyPair() ?: return@retryIfNeeded Promise.ofFail(Error.NoKeyPair) + val userPublicKey = module.storage.getUserPublicKey() ?: return@retryIfNeeded Promise.ofFail(Error.NoKeyPair) + getSingleTargetSnode(publicKey).bind { snode -> + retryIfNeeded(maxRetryCount) { + val signature = ByteArray(Sign.BYTES) + val verificationData = (Snode.Method.DeleteMessage.rawValue + serverHashes.fold("") { a, v -> a + v }).toByteArray() + sodium.cryptoSignDetached(signature, verificationData, verificationData.size.toLong(), userED25519KeyPair.secretKey.asBytes) + val deleteMessageParams = mapOf( + "pubkey" to userPublicKey, + "pubkey_ed25519" to userED25519KeyPair.publicKey.asHexString, + "messages" to serverHashes, + "signature" to Base64.encodeBytes(signature) + ) + invoke(Snode.Method.DeleteMessage, snode, publicKey, deleteMessageParams).map { rawResponse -> + val swarms = rawResponse["swarm"] as? Map ?: return@map mapOf() + val result = swarms.mapNotNull { (hexSnodePublicKey, rawJSON) -> + val json = rawJSON as? Map ?: return@mapNotNull null + val isFailed = json["failed"] as? Boolean ?: false + val statusCode = json["code"] as? String + val reason = json["reason"] as? String + hexSnodePublicKey to if (isFailed) { + Log.e("Loki", "Failed to delete messages from: $hexSnodePublicKey due to error: $reason ($statusCode).") + false + } else { + val hashes = json["deleted"] as List // Hashes of deleted messages + val signature = json["signature"] as String + val snodePublicKey = Key.fromHexString(hexSnodePublicKey) + // The signature looks like ( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] ) + val message = (userPublicKey + serverHashes.fold("") { a, v -> a + v } + hashes.fold("") { a, v -> a + v }).toByteArray() + sodium.cryptoSignVerifyDetached(Base64.decode(signature), message, message.size, snodePublicKey.asBytes) + } + } + return@map result.toMap() + }.fail { e -> + Log.e("Loki", "Failed to delete messages", e) + } + } + } + } + } + // Parsing private fun parseSnodes(rawResponse: Any): List { val json = rawResponse as? Map<*, *> diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt b/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt index 7b1592a52..cfbedb733 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt @@ -7,6 +7,7 @@ class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) { GetSwarm("get_snodes_for_pubkey"), GetMessages("retrieve"), SendMessage("store"), + DeleteMessage("delete"), OxenDaemonRPCCall("oxend_request"), Info("info"), DeleteAll("delete_all") From bf83f9b3917fa518efcb1c412f84cd20c87760a3 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 10 Aug 2021 16:00:06 +1000 Subject: [PATCH 03/43] WIP: message receiver handler for unsend request --- .../attachments/DatabaseAttachmentProvider.kt | 8 ++++++++ .../database/model/MessageRecord.java | 6 +++++- .../database/MessageDataProvider.kt | 2 ++ .../sending_receiving/MessageReceiver.kt | 1 + .../ReceivedMessageHandler.kt | 20 +++++++++++++++++++ 5 files changed, 36 insertions(+), 1 deletion(-) 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 5a13830bd..93ad37e32 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -177,6 +177,14 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) DatabaseFactory.getLokiMessageDatabase(context).deleteMessage(messageID, isSms) } + override fun updateMessageAsDeleted(messageID: Long) { + TODO("Not yet implemented") + } + + override fun getServerHashForMessage(messageID: Long): String? { + TODO("Not yet implemented") + } + override fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? { val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context) return attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index 31db5b451..6c1d5784e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -49,6 +49,7 @@ public abstract class MessageRecord extends DisplayRecord { private final long expireStarted; private final boolean unidentified; public final long id; + public final boolean deleted; public abstract boolean isMms(); public abstract boolean isMmsNotification(); @@ -71,6 +72,7 @@ public abstract class MessageRecord extends DisplayRecord { this.expiresIn = expiresIn; this.expireStarted = expireStarted; this.unidentified = unidentified; + this.deleted = false; } public long getId() { @@ -103,7 +105,9 @@ public abstract class MessageRecord extends DisplayRecord { @Override public SpannableString getDisplayBody(@NonNull Context context) { - if (isGroupUpdateMessage()) { + if (this.deleted) { + return new SpannableString("This message has been deleted."); // TODO: localize + } else if (isGroupUpdateMessage()) { UpdateMessageData updateMessageData = UpdateMessageData.Companion.fromJSON(getBody()); return new SpannableString(UpdateMessageBuilder.INSTANCE.buildGroupUpdateMessage(context, updateMessageData, getIndividualRecipient().getAddress().serialize(), isOutgoing())); } else if (isExpirationTimerUpdate()) { 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 94f59c4b4..86534bb9a 100644 --- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -13,6 +13,8 @@ interface MessageDataProvider { fun getMessageID(serverID: Long): Long? fun getMessageID(serverId: Long, threadId: Long): Pair? fun deleteMessage(messageID: Long, isSms: Boolean) + fun updateMessageAsDeleted(messageID: Long) + fun getServerHashForMessage(messageID: Long): String? fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? fun getAttachmentPointer(attachmentId: Long): SessionServiceAttachmentPointer? 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 99ad38bd5..0a0089210 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 @@ -94,6 +94,7 @@ object MessageReceiver { DataExtractionNotification.fromProto(proto) ?: ExpirationTimerUpdate.fromProto(proto) ?: ConfigurationMessage.fromProto(proto) ?: + UnsendRequest.fromProto(proto) ?: VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage // Ignore self send if needed if (!message.isSelfSendValid && sender == userPublicKey) throw Error.SelfSend diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index cfbd5605b..b514d9672 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -14,6 +14,7 @@ import org.session.libsession.messaging.sending_receiving.link_preview.LinkPrevi import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2 import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.recipients.Recipient @@ -49,6 +50,7 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message) is DataExtractionNotification -> handleDataExtractionNotification(message) is ConfigurationMessage -> handleConfigurationMessage(message) + is UnsendRequest -> handleUnsendRequest(message) is VisibleMessage -> handleVisibleMessage(message, proto, openGroupID) } } @@ -145,6 +147,24 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) { } storage.addContacts(message.contacts) } + +fun MessageReceiver.handleUnsendRequest(message: UnsendRequest) { + if (message.sender != message.author) { return } + val context = MessagingModuleConfiguration.shared.context + val storage = MessagingModuleConfiguration.shared.storage + val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider + val timestamp = message.timestamp ?: return + val author = message.author ?: return + val messageIdToDelete = storage.getMessageIdInDatabase(timestamp, author) ?: return + if (!messageDataProvider.isOutgoingMessage(messageIdToDelete)) { + // TODO: Mark this message as read + // TODO: Cancel the notification of this message + } + messageDataProvider.getServerHashForMessage(messageIdToDelete)?.let { serverHash -> + SnodeAPI.deleteMessage(author, listOf(serverHash)) + } + messageDataProvider.updateMessageAsDeleted(messageIdToDelete) +} //endregion // region Visible Messages From c3e45a308a727965e8eb8a953bf92fe1eb762f79 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 10 Aug 2021 16:42:15 +1000 Subject: [PATCH 04/43] store server hash --- .../attachments/DatabaseAttachmentProvider.kt | 3 ++- .../securesms/database/LokiMessageDatabase.kt | 19 +++++++++++++++++++ .../securesms/database/Storage.kt | 4 ++++ .../database/helpers/SQLCipherOpenHelper.java | 6 ++++++ .../libsession/database/StorageProtocol.kt | 1 + .../libsession/messaging/messages/Message.kt | 1 + .../sending_receiving/MessageSender.kt | 8 ++++++++ 7 files changed, 41 insertions(+), 1 deletion(-) 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 93ad37e32..51835fc88 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -182,7 +182,8 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) } override fun getServerHashForMessage(messageID: Long): String? { - TODO("Not yet implemented") + val messageDB = DatabaseFactory.getLokiMessageDatabase(context) + return messageDB.getMessageServerHash(messageID) } override fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt index c36c197cb..85b345e3f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt @@ -12,12 +12,14 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab private val messageIDTable = "loki_message_friend_request_database" private val messageThreadMappingTable = "loki_message_thread_mapping_database" private val errorMessageTable = "loki_error_message_database" + private val messageHashTable = "loki_message_hash_database" private val messageID = "message_id" private val serverID = "server_id" private val friendRequestStatus = "friend_request_status" private val threadID = "thread_id" private val errorMessage = "error_message" private val messageType = "message_type" + private val serverHash = "server_hash" @JvmStatic val createMessageIDTableCommand = "CREATE TABLE $messageIDTable ($messageID INTEGER PRIMARY KEY, $serverID INTEGER DEFAULT 0, $friendRequestStatus INTEGER DEFAULT 0);" @JvmStatic @@ -28,6 +30,8 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab val updateMessageIDTableForType = "ALTER TABLE $messageIDTable ADD COLUMN $messageType INTEGER DEFAULT 0; ALTER TABLE $messageIDTable ADD CONSTRAINT PK_$messageIDTable PRIMARY KEY ($messageID, $serverID);" @JvmStatic val updateMessageMappingTable = "ALTER TABLE $messageThreadMappingTable ADD COLUMN $serverID INTEGER DEFAULT 0; ALTER TABLE $messageThreadMappingTable ADD CONSTRAINT PK_$messageThreadMappingTable PRIMARY KEY ($messageID, $serverID);" + @JvmStatic + val createMessageHashTableCommand = "CREATE TABLE IF NOT EXISTS $messageHashTable ($messageID INTEGER PRIMARY KEY, $serverHash STRING);" const val SMS_TYPE = 0 const val MMS_TYPE = 1 @@ -150,4 +154,19 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab database.endTransaction() } } + + fun getMessageServerHash(messageID: Long): String? { + val database = databaseHelper.readableDatabase + return database.get(messageHashTable, "${Companion.messageID} = ?", arrayOf(messageID.toString())) { cursor -> + cursor.getString(serverHash) + } + } + + fun setMessageServerHash(messageID: Long, serverHash: String) { + val database = databaseHelper.writableDatabase + val contentValues = ContentValues(2) + contentValues.put(Companion.messageID, messageID) + contentValues.put(Companion.serverHash, serverHash) + database.insertOrUpdate(messageHashTable, contentValues, "${Companion.messageID} = ?", arrayOf(messageID.toString())) + } } \ No newline at end of file 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 fe9327a13..66332bed6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -358,6 +358,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } } + override fun setMessageServerHash(messageID: Long, serverHash: String) { + DatabaseFactory.getLokiMessageDatabase(context).setMessageServerHash(messageID, serverHash) + } + override fun getGroup(groupID: String): GroupRecord? { val group = DatabaseFactory.getGroupDatabase(context).getGroup(groupID) return if (group.isPresent) { group.get() } else null diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 106528ee2..a7fa86cb5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -59,6 +59,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV25 = 46; private static final int lokiV26 = 47; private static final int lokiV27 = 48; + private static final int lokiV28 = 49; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes private static final int DATABASE_VERSION = lokiV27; @@ -123,6 +124,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand()); db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand()); db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand()); + db.execSQL(LokiMessageDatabase.getCreateMessageHashTableCommand()); db.execSQL(LokiThreadDatabase.getCreateSessionResetTableCommand()); db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand()); db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand()); @@ -302,6 +304,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(RecipientDatabase.getCreateNotificationTypeCommand()); } + if (oldVersion < lokiV28) { + db.execSQL(LokiMessageDatabase.getCreateMessageHashTableCommand()); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index bc7324d21..3c8e0c712 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -93,6 +93,7 @@ interface StorageProtocol { fun markAsSent(timestamp: Long, author: String) fun markUnidentified(timestamp: Long, author: String) fun setErrorMessage(timestamp: Long, author: String, error: Exception) + fun setMessageServerHash(messageID: Long, serverHash: String) // Closed Groups fun getGroup(groupID: String): GroupRecord? diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt index 56779795e..d201daa98 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt @@ -13,6 +13,7 @@ abstract class Message { var sender: String? = null var groupPublicKey: String? = null var openGroupServerMessageID: Long? = null + var serverHash: String? = null open val ttl: Long = 14 * 24 * 60 * 60 * 1000 open val isSelfSendValid: Boolean = false 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 4758525ab..aa78074ed 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 @@ -161,6 +161,8 @@ object MessageSender { if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { SnodeModule.shared.broadcaster.broadcast("messageSent", message.sentTimestamp!!) } + val hash = it["hash"] as? String + message.serverHash = hash handleSuccessfulMessageSend(message, destination, isSyncMessage) var shouldNotify = (message is VisibleMessage && !isSyncMessage) /* @@ -259,6 +261,12 @@ object MessageSender { storage.updateSentTimestamp(messageID, message.isMediaMessage(), openGroupSentTimestamp, message.threadID!!) message.sentTimestamp = openGroupSentTimestamp } + // When the sync message is successfully sent, the hash value of this TSOutgoingMessage + // will be replaced by the hash value of the sync message. Since the hash value of the + // real message has no use when we delete a message. It is OK to let it be. + message.serverHash?.let { + storage.setMessageServerHash(messageID, it) + } // Track the open group server message ID if (message.openGroupServerMessageID != null && destination is Destination.OpenGroupV2) { val encoded = GroupUtil.getEncodedOpenGroupID("${destination.server}.${destination.room}".toByteArray()) From 0ebb87ef6fcb82135f51bfc983d7ec958f7b5a11 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 10 Aug 2021 16:47:52 +1000 Subject: [PATCH 05/43] allow self send and notify for unsend requests --- .../libsession/messaging/sending_receiving/MessageSender.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 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 aa78074ed..0de835076 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 @@ -11,6 +11,7 @@ import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate +import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.visible.* import org.session.libsession.messaging.open_groups.* import org.session.libsession.messaging.utilities.MessageWrapper @@ -94,7 +95,7 @@ object MessageSender { // • a closed group control message of type `new` var isNewClosedGroupControlMessage = false if (message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) isNewClosedGroupControlMessage = true - if (isSelfSend && message !is ConfigurationMessage && !isSyncMessage && !isNewClosedGroupControlMessage) { + if (isSelfSend && message !is ConfigurationMessage && !isSyncMessage && !isNewClosedGroupControlMessage && message !is UnsendRequest) { handleSuccessfulMessageSend(message, destination) deferred.resolve(Unit) return promise @@ -164,7 +165,7 @@ object MessageSender { val hash = it["hash"] as? String message.serverHash = hash handleSuccessfulMessageSend(message, destination, isSyncMessage) - var shouldNotify = (message is VisibleMessage && !isSyncMessage) + var shouldNotify = ((message is VisibleMessage || message is UnsendRequest) && !isSyncMessage) /* if (message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) { shouldNotify = true From 5264d4e2f857bea8dda7d7cd60875b25417b3a5a Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 11 Aug 2021 15:12:10 +1000 Subject: [PATCH 06/43] remove weird white space --- .../src/main/java/org/session/libsession/snode/SnodeAPI.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d297ba588..3bc826970 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -52,7 +52,7 @@ object SnodeAPI { if (useTestnet) { setOf( "http://public.loki.foundation:38157" ) } else { - setOf( "https://storage.seed1.loki.network:$seedNodePort ", "https://storage.seed3.loki.network:$seedNodePort ", "https://public.loki.foundation:$seedNodePort" ) + setOf( "https://storage.seed1.loki.network:$seedNodePort", "https://storage.seed3.loki.network:$seedNodePort", "https://public.loki.foundation:$seedNodePort" ) } } private val snodeFailureThreshold = 3 From af6c19690bf76ebac20c7e24a23d005d501771ad Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 11 Aug 2021 15:17:53 +1000 Subject: [PATCH 07/43] send unsend request & delete remotely --- .../attachments/DatabaseAttachmentProvider.kt | 1 + .../conversation/v2/ConversationActivityV2.kt | 78 +++++++++++++------ .../securesms/database/LokiMessageDatabase.kt | 5 ++ .../database/helpers/SQLCipherOpenHelper.java | 2 +- 4 files changed, 60 insertions(+), 26 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 51835fc88..076a1e5c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -175,6 +175,7 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) db.delete(messageID) } DatabaseFactory.getLokiMessageDatabase(context).deleteMessage(messageID, isSms) + DatabaseFactory.getLokiMessageDatabase(context).deleteMessageServerHash(messageID) } override fun updateMessageAsDeleted(messageID: Long) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 4a86d5edc..e90dc830a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -50,6 +50,7 @@ import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.mentions.Mention import org.session.libsession.messaging.mentions.MentionsManager import org.session.libsession.messaging.messages.control.DataExtractionNotification +import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.visible.OpenGroupInvitation @@ -59,6 +60,7 @@ import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.MediaTypes @@ -1114,38 +1116,64 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe stopAudioHandler.removeCallbacks(stopVoiceMessageRecordingTask) } - override fun deleteMessages(messages: Set) { - val messageCount = messages.size + private fun buildUsendRequest(message: MessageRecord): UnsendRequest? { + if (this.thread.isOpenGroupRecipient) return null + val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider + messageDataProvider.getServerHashForMessage(message.id) ?: return null + val unsendRequest = UnsendRequest() + if (message.isOutgoing) { + unsendRequest.author = TextSecurePreferences.getLocalNumber(this) + } else { + unsendRequest.author = message.individualRecipient.address.contactIdentifier() + } + unsendRequest.timestamp = message.timestamp + + return unsendRequest + } + + private fun deleteLocally(message: MessageRecord) { + buildUsendRequest(message)?.let { unsendRequest -> + MessageSender.send(unsendRequest, thread.address) + } + MessagingModuleConfiguration.shared.messageDataProvider.deleteMessage(message.id, !message.isMms) + } + + private fun deleteForEveryone(message: MessageRecord) { + buildUsendRequest(message)?.let { unsendRequest -> + MessageSender.send(unsendRequest, thread.address) + } val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val messageDB = DatabaseFactory.getLokiMessageDatabase(this@ConversationActivityV2) + val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID) + if (openGroup != null) { + messageDB.getServerID(message.id, !message.isMms)?.let { messageServerID -> + OpenGroupAPIV2.deleteMessage(messageServerID, openGroup.room, openGroup.server) + .success { + messageDataProvider.deleteMessage(message.id, !message.isMms) + }.failUi { error -> + Toast.makeText(this@ConversationActivityV2, "Couldn't delete message due to error: $error", Toast.LENGTH_LONG).show() + } + } + } else { + messageDataProvider.getServerHashForMessage(message.id)?.let { serverHash -> + SnodeAPI.deleteMessage(thread.address.serialize(), listOf(serverHash)) + .failUi { error -> + Toast.makeText(this@ConversationActivityV2, "Couldn't delete message due to error: $error", Toast.LENGTH_LONG).show() + } + } + messageDataProvider.deleteMessage(message.id, !message.isMms) + } + } + + override fun deleteMessages(messages: Set) { + val messageCount = messages.size val builder = AlertDialog.Builder(this) builder.setTitle(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount)) builder.setMessage(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount)) builder.setCancelable(true) - val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID) builder.setPositiveButton(R.string.delete) { _, _ -> - if (openGroup != null) { - val messageServerIDs = mutableMapOf() - for (message in messages) { - val messageServerID = messageDB.getServerID(message.id, !message.isMms) ?: continue - messageServerIDs[messageServerID] = message - } - for ((messageServerID, message) in messageServerIDs) { - OpenGroupAPIV2.deleteMessage(messageServerID, openGroup.room, openGroup.server) - .success { - messageDataProvider.deleteMessage(message.id, !message.isMms) - }.failUi { error -> - Toast.makeText(this@ConversationActivityV2, "Couldn't delete message due to error: $error", Toast.LENGTH_LONG).show() - } - } - } else { - for (message in messages) { - if (message.isMms) { - DatabaseFactory.getMmsDatabase(this@ConversationActivityV2).delete(message.id) - } else { - DatabaseFactory.getSmsDatabase(this@ConversationActivityV2).deleteMessage(message.id) - } - } + for (message in messages) { + this.deleteForEveryone(message) } endActionMode() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt index 85b345e3f..07513ec32 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt @@ -169,4 +169,9 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab contentValues.put(Companion.serverHash, serverHash) database.insertOrUpdate(messageHashTable, contentValues, "${Companion.messageID} = ?", arrayOf(messageID.toString())) } + + fun deleteMessageServerHash(messageID: Long) { + val database = databaseHelper.writableDatabase + database.delete(messageHashTable, "${Companion.messageID} = ?", arrayOf(messageID.toString())) + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index a7fa86cb5..ccf5c9e77 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -62,7 +62,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV28 = 49; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV27; + private static final int DATABASE_VERSION = lokiV28; private static final String DATABASE_NAME = "signal.db"; private final Context context; From 0000aa0d13b39b2d3863d300900a0d8428ef746f Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 11 Aug 2021 15:24:33 +1000 Subject: [PATCH 08/43] delete locally after successfully delete remotely --- .../securesms/conversation/v2/ConversationActivityV2.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index e90dc830a..7abe4a704 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -1157,11 +1157,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } else { messageDataProvider.getServerHashForMessage(message.id)?.let { serverHash -> SnodeAPI.deleteMessage(thread.address.serialize(), listOf(serverHash)) - .failUi { error -> + .success { + messageDataProvider.deleteMessage(message.id, !message.isMms) + }.failUi { error -> Toast.makeText(this@ConversationActivityV2, "Couldn't delete message due to error: $error", Toast.LENGTH_LONG).show() } } - messageDataProvider.deleteMessage(message.id, !message.isMms) } } From 77b5b3ab01e144375a371654b05e22ff65c3a8e8 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 11 Aug 2021 15:36:13 +1000 Subject: [PATCH 09/43] deleted message bubble ui --- .../v2/messages/DeletedMessageView.kt | 35 +++++++++++++++++++ .../main/res/layout/view_deleted_message.xml | 30 ++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt create mode 100644 app/src/main/res/layout/view_deleted_message.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt new file mode 100644 index 000000000..3adfc125f --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt @@ -0,0 +1,35 @@ +package org.thoughtcrime.securesms.conversation.v2.messages + +import android.content.Context +import android.content.res.ColorStateList +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.LinearLayout +import androidx.annotation.ColorInt +import kotlinx.android.synthetic.main.fragment_conversation_bottom_sheet.view.* +import kotlinx.android.synthetic.main.view_deleted_message.view.* +import kotlinx.android.synthetic.main.view_document.view.* +import network.loki.messenger.R +import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.database.model.MmsMessageRecord + +class DeletedMessageView : LinearLayout { + + // region Lifecycle + constructor(context: Context) : super(context) { initialize() } + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } + + private fun initialize() { + LayoutInflater.from(context).inflate(R.layout.view_deleted_message, this) + } + // endregion + + // region Updating + fun bind(message: MessageRecord, @ColorInt textColor: Int) { + deleteTextView.text = "This message has been deleted" + deleteTextView.setTextColor(textColor) + deletedMessageViewIconImageView.imageTintList = ColorStateList.valueOf(textColor) + } + // endregion +} \ No newline at end of file diff --git a/app/src/main/res/layout/view_deleted_message.xml b/app/src/main/res/layout/view_deleted_message.xml new file mode 100644 index 000000000..20b6e6104 --- /dev/null +++ b/app/src/main/res/layout/view_deleted_message.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file From 23a61299aca3a9d8ed0915fa878fecfb1af7b7f0 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 11 Aug 2021 16:35:48 +1000 Subject: [PATCH 10/43] bind deleted message view --- .../conversation/v2/messages/DeletedMessageView.kt | 4 +++- .../conversation/v2/messages/VisibleMessageContentView.kt | 6 +++++- app/src/main/res/values/strings.xml | 5 +++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt index 3adfc125f..96091428e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt @@ -12,6 +12,7 @@ import kotlinx.android.synthetic.main.view_document.view.* import network.loki.messenger.R import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord +import java.util.* class DeletedMessageView : LinearLayout { @@ -27,7 +28,8 @@ class DeletedMessageView : LinearLayout { // region Updating fun bind(message: MessageRecord, @ColorInt textColor: Int) { - deleteTextView.text = "This message has been deleted" + assert(message.deleted) + deleteTextView.text = context.getString(R.string.deleted_message) deleteTextView.setTextColor(textColor) deletedMessageViewIconImageView.imageTintList = ColorStateList.valueOf(textColor) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 8f2410652..dea9122a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -76,7 +76,11 @@ class VisibleMessageContentView : LinearLayout { mainContainer.removeAllViews() onContentClick = null onContentDoubleTap = null - if (message is MmsMessageRecord && message.linkPreviews.isNotEmpty()) { + if (message.deleted) { + val deletedMessageView = DeletedMessageView(context) + deletedMessageView.bind(message, VisibleMessageContentView.getTextColor(context,message)) + mainContainer.addView(deletedMessageView) + } else if (message is MmsMessageRecord && message.linkPreviews.isNotEmpty()) { val linkPreviewView = LinkPreviewView(context) linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster, searchQuery) mainContainer.addView(linkPreviewView) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9fa0283ac..360bd4bec 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -893,4 +893,9 @@ Send All Mentions + This message has been deleted + Delete just for me + Delete for everyone + Delete for me and %s + From 8b6b02911f895ab8463b733e355ad7f343ec6434 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 12 Aug 2021 11:43:33 +1000 Subject: [PATCH 11/43] handle database for message deleted by unsend request --- .../attachments/DatabaseAttachmentProvider.kt | 5 ++++- .../v2/messages/DeletedMessageView.kt | 6 +++--- .../v2/messages/VisibleMessageContentView.kt | 2 +- .../securesms/database/MessagingDatabase.java | 2 ++ .../securesms/database/MmsDatabase.java | 16 ++++++++++++++++ .../securesms/database/MmsSmsColumns.java | 3 +++ .../securesms/database/SmsDatabase.java | 10 ++++++++++ .../securesms/database/model/DisplayRecord.java | 1 + .../securesms/database/model/MessageRecord.java | 6 +----- app/src/main/res/layout/view_deleted_message.xml | 10 ++++++---- 10 files changed, 47 insertions(+), 14 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 076a1e5c0..99153ae67 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -179,7 +179,10 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) } override fun updateMessageAsDeleted(messageID: Long) { - TODO("Not yet implemented") + val smsDatabase = DatabaseFactory.getSmsDatabase(context) + val mmsDatabase = DatabaseFactory.getMmsDatabase(context) + smsDatabase.markAsDeleted(messageID) + mmsDatabase.markAsDeleted(messageID) } override fun getServerHashForMessage(messageID: Long): String? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt index 96091428e..a91473352 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt @@ -28,9 +28,9 @@ class DeletedMessageView : LinearLayout { // region Updating fun bind(message: MessageRecord, @ColorInt textColor: Int) { - assert(message.deleted) - deleteTextView.text = context.getString(R.string.deleted_message) - deleteTextView.setTextColor(textColor) + assert(message.isDeleted) + deleteTitleTextView.text = context.getString(R.string.deleted_message) + deleteTitleTextView.setTextColor(textColor) deletedMessageViewIconImageView.imageTintList = ColorStateList.valueOf(textColor) } // endregion diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index dea9122a1..558bc1361 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -76,7 +76,7 @@ class VisibleMessageContentView : LinearLayout { mainContainer.removeAllViews() onContentClick = null onContentDoubleTap = null - if (message.deleted) { + if (message.isDeleted) { val deletedMessageView = DeletedMessageView(context) deletedMessageView.bind(message, VisibleMessageContentView.getTextColor(context,message)) mainContainer.addView(deletedMessageView) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java index 0fcb61d74..d6dd09764 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java @@ -38,6 +38,8 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn public abstract void markAsSent(long messageId, boolean secure); public abstract void markUnidentified(long messageId, boolean unidentified); + public abstract void markAsDeleted(long messageId); + public void addMismatchedIdentity(long messageId, Address address, IdentityKey identityKey) { try { addToDocument(messageId, MISMATCHED_IDENTITIES, 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 38e9eeccc..597d14f09 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -391,6 +391,22 @@ public class MmsDatabase extends MessagingDatabase { db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); } + @Override + public void markAsDeleted(long messageId) { + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + ContentValues contentValues = new ContentValues(); + contentValues.put(READ, 1); + contentValues.put(BODY, ""); + database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); + + AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context); + ThreadUtils.queue(() -> attachmentDatabase.deleteAttachmentsForMessage(messageId)); + + long threadId = getThreadIdForMessage(messageId); + updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE, Optional.of(threadId)); + notifyConversationListeners(threadId); + } + @Override public void markExpireStarted(long messageId) { markExpireStarted(messageId, System.currentTimeMillis()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 81ec17bd0..52642b5d1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -40,6 +40,7 @@ public interface MmsSmsColumns { protected static final long BASE_PENDING_SECURE_SMS_FALLBACK = 25; protected static final long BASE_PENDING_INSECURE_SMS_FALLBACK = 26; public static final long BASE_DRAFT_TYPE = 27; + protected static final long BASE_DELETED_TYPE = 28; protected static final long[] OUTGOING_MESSAGE_TYPES = {BASE_OUTBOX_TYPE, BASE_SENT_TYPE, BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE, @@ -152,6 +153,8 @@ public interface MmsSmsColumns { return (type & BASE_TYPE_MASK) == BASE_INBOX_TYPE; } + public static boolean isDeletedMessage(long type) { return (type & BASE_TYPE_MASK) == BASE_DELETED_TYPE; } + public static boolean isJoinedType(long type) { return (type & BASE_TYPE_MASK) == JOINED_TYPE; } 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 c148f8ebc..eaa289807 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -183,6 +183,16 @@ public class SmsDatabase extends MessagingDatabase { db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)}); } + @Override + public void markAsDeleted(long messageId) { + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + ContentValues contentValues = new ContentValues(); + contentValues.put(READ, 1); + contentValues.put(BODY, ""); + database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); + updateTypeBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE); + } + @Override public void markExpireStarted(long id) { markExpireStarted(id, System.currentTimeMillis()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index 3adb9cbda..10e4cb753 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -117,6 +117,7 @@ public abstract class DisplayRecord { public boolean isMissedCall() { return SmsDatabase.Types.isMissedCall(type); } + public boolean isDeleted() { return MmsSmsColumns.Types.isDeletedMessage(type); } public boolean isControlMessage() { return isGroupUpdateMessage() || isExpirationTimerUpdate() || isDataExtractionNotification(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index 6c1d5784e..31db5b451 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -49,7 +49,6 @@ public abstract class MessageRecord extends DisplayRecord { private final long expireStarted; private final boolean unidentified; public final long id; - public final boolean deleted; public abstract boolean isMms(); public abstract boolean isMmsNotification(); @@ -72,7 +71,6 @@ public abstract class MessageRecord extends DisplayRecord { this.expiresIn = expiresIn; this.expireStarted = expireStarted; this.unidentified = unidentified; - this.deleted = false; } public long getId() { @@ -105,9 +103,7 @@ public abstract class MessageRecord extends DisplayRecord { @Override public SpannableString getDisplayBody(@NonNull Context context) { - if (this.deleted) { - return new SpannableString("This message has been deleted."); // TODO: localize - } else if (isGroupUpdateMessage()) { + if (isGroupUpdateMessage()) { UpdateMessageData updateMessageData = UpdateMessageData.Companion.fromJSON(getBody()); return new SpannableString(UpdateMessageBuilder.INSTANCE.buildGroupUpdateMessage(context, updateMessageData, getIndividualRecipient().getAddress().serialize(), isOutgoing())); } else if (isExpirationTimerUpdate()) { diff --git a/app/src/main/res/layout/view_deleted_message.xml b/app/src/main/res/layout/view_deleted_message.xml index 20b6e6104..aed760259 100644 --- a/app/src/main/res/layout/view_deleted_message.xml +++ b/app/src/main/res/layout/view_deleted_message.xml @@ -6,21 +6,23 @@ android:layout_height="wrap_content" xmlns:tools="http://schemas.android.com/tools" android:orientation="horizontal" - android:padding="@dimen/medium_spacing" + android:padding="@dimen/small_spacing" android:gravity="center"> Date: Thu, 12 Aug 2021 14:14:37 +1000 Subject: [PATCH 12/43] handle home screen update for unsend request --- .../attachments/DatabaseAttachmentProvider.kt | 17 ++++++++++++----- .../securesms/database/MessagingDatabase.java | 2 +- .../securesms/database/MmsDatabase.java | 3 ++- .../securesms/database/MmsSmsDatabase.java | 4 +++- .../securesms/database/SmsDatabase.java | 4 +++- .../securesms/database/ThreadDatabase.java | 19 ++++++++++++++++--- .../database/MessageDataProvider.kt | 2 +- .../ReceivedMessageHandler.kt | 3 +-- 8 files changed, 39 insertions(+), 15 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 99153ae67..aa1fe66f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -178,11 +178,18 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) DatabaseFactory.getLokiMessageDatabase(context).deleteMessageServerHash(messageID) } - override fun updateMessageAsDeleted(messageID: Long) { - val smsDatabase = DatabaseFactory.getSmsDatabase(context) - val mmsDatabase = DatabaseFactory.getMmsDatabase(context) - smsDatabase.markAsDeleted(messageID) - mmsDatabase.markAsDeleted(messageID) + override fun updateMessageAsDeleted(timestamp: Long, author: String) { + val database = DatabaseFactory.getMmsSmsDatabase(context) + val address = Address.fromSerialized(author) + val message = database.getMessageFor(timestamp, address)!! + if (message.isMms) { + val mmsDatabase = DatabaseFactory.getMmsDatabase(context) + mmsDatabase.markAsDeleted(message.id, message.isRead) + } else { + val smsDatabase = DatabaseFactory.getSmsDatabase(context) + smsDatabase.markAsDeleted(message.id, message.isRead) + } + } override fun getServerHashForMessage(messageID: Long): String? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java index d6dd09764..f542058c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java @@ -38,7 +38,7 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn public abstract void markAsSent(long messageId, boolean secure); public abstract void markUnidentified(long messageId, boolean unidentified); - public abstract void markAsDeleted(long messageId); + public abstract void markAsDeleted(long messageId, boolean read); public void addMismatchedIdentity(long messageId, Address address, IdentityKey identityKey) { try { 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 597d14f09..1fa26000a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -392,7 +392,7 @@ public class MmsDatabase extends MessagingDatabase { } @Override - public void markAsDeleted(long messageId) { + public void markAsDeleted(long messageId, boolean read) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put(READ, 1); @@ -403,6 +403,7 @@ public class MmsDatabase extends MessagingDatabase { ThreadUtils.queue(() -> attachmentDatabase.deleteAttachmentsForMessage(messageId)); long threadId = getThreadIdForMessage(messageId); + if (!read) { DatabaseFactory.getThreadDatabase(context).decrementUnread(threadId, 1); } updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE, Optional.of(threadId)); notifyConversationListeners(threadId); } 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 42284a4ff..150f7f4c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -129,7 +129,9 @@ public class MmsSmsDatabase extends Database { String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; - return queryTables(PROJECTION, selection, order, "1"); + // FIXME: Not sure if this will cause any performance issues + // return queryTables(PROJECTION, selection, order, "1"); + return queryTables(PROJECTION, selection, order, null); } public long getLastMessageID(long threadId) { 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 eaa289807..5b7503763 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -184,12 +184,14 @@ public class SmsDatabase extends MessagingDatabase { } @Override - public void markAsDeleted(long messageId) { + public void markAsDeleted(long messageId, boolean read) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put(READ, 1); contentValues.put(BODY, ""); database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); + long threadId = getThreadIdForMessage(messageId); + if (!read) { DatabaseFactory.getThreadDatabase(context).decrementUnread(threadId, 1); } updateTypeBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index c9fa010e8..3d894a046 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -294,6 +294,14 @@ public class ThreadDatabase extends Database { String.valueOf(threadId)}); } + public void decrementUnread(long threadId, int amount) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " + + UNREAD_COUNT + " = " + UNREAD_COUNT + " - ? WHERE " + ID + " = ?", + new String[] {String.valueOf(amount), + String.valueOf(threadId)}); + } + public void setDistributionType(long threadId, int distributionType) { ContentValues contentValues = new ContentValues(1); contentValues.put(TYPE, distributionType); @@ -536,9 +544,14 @@ public class ThreadDatabase extends Database { try { reader = mmsSmsDatabase.readerFor(mmsSmsDatabase.getConversationSnippet(threadId)); - MessageRecord record; - - if (reader != null && (record = reader.getNext()) != null) { + MessageRecord record = null; + if (reader != null) { + record = reader.getNext(); + while (record != null && record.isDeleted()) { + record = reader.getNext(); + } + } + if (record != null && !record.isDeleted()) { updateThread(threadId, count, getFormattedBodyFor(record), getAttachmentUriFor(record), record.getTimestamp(), record.getDeliveryStatus(), record.getDeliveryReceiptCount(), record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount()); 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 86534bb9a..356307d1d 100644 --- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -13,7 +13,7 @@ interface MessageDataProvider { fun getMessageID(serverID: Long): Long? fun getMessageID(serverId: Long, threadId: Long): Pair? fun deleteMessage(messageID: Long, isSms: Boolean) - fun updateMessageAsDeleted(messageID: Long) + fun updateMessageAsDeleted(timestamp: Long, author: String) fun getServerHashForMessage(messageID: Long): String? fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index b514d9672..1d5a797b7 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -157,13 +157,12 @@ fun MessageReceiver.handleUnsendRequest(message: UnsendRequest) { val author = message.author ?: return val messageIdToDelete = storage.getMessageIdInDatabase(timestamp, author) ?: return if (!messageDataProvider.isOutgoingMessage(messageIdToDelete)) { - // TODO: Mark this message as read // TODO: Cancel the notification of this message } messageDataProvider.getServerHashForMessage(messageIdToDelete)?.let { serverHash -> SnodeAPI.deleteMessage(author, listOf(serverHash)) } - messageDataProvider.updateMessageAsDeleted(messageIdToDelete) + messageDataProvider.updateMessageAsDeleted(timestamp, author) } //endregion From 9dcc5dd848943a141abc1a6e1229e90715c0d78d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 12 Aug 2021 15:01:48 +1000 Subject: [PATCH 13/43] disable interaction for deleted messages --- .../securesms/conversation/v2/ConversationAdapter.kt | 8 +++++--- .../conversation/v2/messages/VisibleMessageView.kt | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 15de18a60..59e9f4d6f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -73,9 +73,11 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr val position = viewHolder.adapterPosition view.indexInAdapter = position view.bind(message, getMessageBefore(position, cursor), getMessageAfter(position, cursor), glide, searchQuery) - view.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, view, event) } - view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) } - view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) } + if (!message.isDeleted) { + view.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, view, event) } + view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) } + view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) } + } view.contentViewDelegate = visibleMessageContentViewDelegate } is ControlMessageViewHolder -> viewHolder.view.bind(message) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index ad1864d05..5e5b1da22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -266,6 +266,7 @@ class VisibleMessageView : LinearLayout { // region Interaction override fun onTouchEvent(event: MotionEvent): Boolean { + if (onPress == null || onSwipeToReply == null || onLongPress == null) { return false } when (event.action) { MotionEvent.ACTION_DOWN -> onDown(event) MotionEvent.ACTION_MOVE -> onMove(event) From 7e791d63dc004fd29b391c1892a2f31b8d5dfbd8 Mon Sep 17 00:00:00 2001 From: Harris Date: Thu, 12 Aug 2021 15:36:08 +1000 Subject: [PATCH 14/43] fix: other view types have clickable links, clickable links in the long message. Ordering of highlight mentions before getting clickable spans fixes #664 --- .../v2/components/AlbumThumbnailView.kt | 15 ++++++++-- .../v2/messages/VisibleMessageContentView.kt | 30 ++++++++++++------- .../longmessage/LongMessageActivity.java | 15 +++------- .../main/res/layout/album_thumbnail_view.xml | 2 +- .../main/res/layout/longmessage_activity.xml | 2 ++ 5 files changed, 39 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt index 7cd78cda2..b1283e1a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt @@ -21,13 +21,14 @@ import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.components.CornerMask +import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentView import org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView +import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans import org.thoughtcrime.securesms.database.model.MmsMessageRecord -import org.thoughtcrime.securesms.util.ActivityDispatcher import org.thoughtcrime.securesms.longmessage.LongMessageActivity import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.Slide -import org.thoughtcrime.securesms.video.exo.AttachmentDataSource +import org.thoughtcrime.securesms.util.ActivityDispatcher import kotlin.math.roundToInt class AlbumThumbnailView : FrameLayout { @@ -80,6 +81,13 @@ class AlbumThumbnailView : FrameLayout { } return } + val intersectedSpans = albumCellBodyText.getIntersectedModalSpans(eventRect) + if (intersectedSpans.isNotEmpty()) { + intersectedSpans.forEach { span -> + span.onClick(albumCellBodyText) + } + return + } // test each album child albumCellContainer.findViewById(R.id.album_thumbnail_root)?.children?.forEachIndexed { index, child -> child.getGlobalVisibleRect(testRect) @@ -130,7 +138,8 @@ class AlbumThumbnailView : FrameLayout { thumbnailView.setImageResource(glideRequests, slide, isPreview = false, mms = message) } albumCellBodyParent.isVisible = message.body.isNotEmpty() - albumCellBodyText.text = message.body + val body = VisibleMessageContentView.getBodySpans(context, message, null) + albumCellBodyText.text = body post { // post to await layout of text albumCellBodyText.layout?.let { layout -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 8f2410652..8db00031f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.Color import android.graphics.Rect import android.graphics.drawable.Drawable +import android.text.Spannable import android.text.style.BackgroundColorSpan import android.text.style.ForegroundColorSpan import android.text.style.URLSpan @@ -28,21 +29,21 @@ import okhttp3.HttpUrl import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.recipients.Recipient -import org.thoughtcrime.securesms.conversation.v2.components.AlbumThumbnailView import org.thoughtcrime.securesms.components.emoji.EmojiTextView import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 +import org.thoughtcrime.securesms.conversation.v2.components.AlbumThumbnailView import org.thoughtcrime.securesms.conversation.v2.dialogs.OpenURLDialog import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord -import org.thoughtcrime.securesms.util.* import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.util.SearchUtil import org.thoughtcrime.securesms.util.SearchUtil.StyleFactory import org.thoughtcrime.securesms.util.UiModeUtilities -import java.net.IDN +import org.thoughtcrime.securesms.util.getColorWithID +import org.thoughtcrime.securesms.util.toPx import java.util.* import kotlin.math.roundToInt @@ -106,6 +107,10 @@ class VisibleMessageContentView : LinearLayout { quoteView.getGlobalVisibleRect(r) if (r.contains(event.rawX.roundToInt(), event.rawY.roundToInt())) { delegate?.scrollToMessageIfPossible(quote.id) + } else { + bodyTextView.getIntersectedModalSpans(event).forEach { span -> + span.onClick(bodyTextView) + } } } } else if (message is MmsMessageRecord && message.slideDeck.audioSlide != null) { @@ -209,7 +214,18 @@ class VisibleMessageContentView : LinearLayout { val color = getTextColor(context, message) result.setTextColor(color) result.setLinkTextColor(color) + val body = getBodySpans(context, message, searchQuery) + result.text = body + return result + } + + fun getBodySpans(context: Context, message: MessageRecord, searchQuery: String?): Spannable { var body = message.body.toSpannable() + + body = MentionUtilities.highlightMentions(body, message.isOutgoing, message.threadId, context) + body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { BackgroundColorSpan(Color.WHITE) }, body, searchQuery) + body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { ForegroundColorSpan(Color.BLACK) }, body, searchQuery) + Linkify.addLinks(body, Linkify.WEB_URLS) // replace URLSpans with ModalURLSpans @@ -225,13 +241,7 @@ class VisibleMessageContentView : LinearLayout { body.removeSpan(urlSpan) body.setSpan(replacementSpan, start, end, flags) } - - body = MentionUtilities.highlightMentions(body, message.isOutgoing, message.threadId, context) - body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { BackgroundColorSpan(Color.WHITE) }, body, searchQuery) - body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { ForegroundColorSpan(Color.BLACK) }, body, searchQuery) - - result.text = body - return result + return body } @ColorInt diff --git a/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java b/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java index 04edf1194..68c568bb3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageActivity.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.longmessage; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.text.Spannable; import android.text.method.LinkMovementMethod; import android.view.MenuItem; import android.widget.TextView; @@ -15,7 +16,7 @@ import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Util; import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; -import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities; +import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentView; import network.loki.messenger.R; @@ -81,18 +82,10 @@ public class LongMessageActivity extends PassphraseRequiredActionBarActivity { String name = Util.getFirstNonEmpty(recipient.getName(), recipient.getProfileName(), recipient.getAddress().serialize()); getSupportActionBar().setTitle(getString(R.string.LongMessageActivity_message_from_s, name)); } - - String trimmedBody = getTrimmedBody(message.get().getFullBody()); - String mentionBody = MentionUtilities.highlightMentions(trimmedBody, message.get().getMessageRecord().getThreadId(), this); - - textBody.setText(mentionBody); + Spannable bodySpans = VisibleMessageContentView.Companion.getBodySpans(this, message.get().getMessageRecord(), null); + textBody.setText(bodySpans); textBody.setMovementMethod(LinkMovementMethod.getInstance()); }); } - private String getTrimmedBody(@NonNull String text) { - return text.length() <= MAX_DISPLAY_LENGTH ? text - : text.substring(0, MAX_DISPLAY_LENGTH); - } - } diff --git a/app/src/main/res/layout/album_thumbnail_view.xml b/app/src/main/res/layout/album_thumbnail_view.xml index 9c7ed0221..706964a20 100644 --- a/app/src/main/res/layout/album_thumbnail_view.xml +++ b/app/src/main/res/layout/album_thumbnail_view.xml @@ -52,7 +52,7 @@ android:layout_width="@dimen/accent_line_thickness" android:layout_height="match_parent" android:background="@color/accent"/> - From 4f89c336766e8cd3fdb2adbca90c2b5ada6b5203 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 12 Aug 2021 16:37:53 +1000 Subject: [PATCH 15/43] fix a issue that the unread count can be negative --- .../org/thoughtcrime/securesms/database/ThreadDatabase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 3d894a046..361d1449e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -297,7 +297,7 @@ public class ThreadDatabase extends Database { public void decrementUnread(long threadId, int amount) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " + - UNREAD_COUNT + " = " + UNREAD_COUNT + " - ? WHERE " + ID + " = ?", + UNREAD_COUNT + " = " + UNREAD_COUNT + " - ? WHERE " + ID + " = ? AND " + UNREAD_COUNT + " > 0", new String[] {String.valueOf(amount), String.valueOf(threadId)}); } From 266ed2fa78d696c6bcf039ff54333f5debd41334 Mon Sep 17 00:00:00 2001 From: Harris Date: Thu, 12 Aug 2021 17:50:35 +1000 Subject: [PATCH 16/43] feat: allow opening conversation thread from UserDetailsBottomSheet.kt refactor the copy button logic to occur when long pressing the Session ID text to fix #517 --- .../securesms/home/UserDetailsBottomSheet.kt | 17 ++++++++++++++++- .../fragment_user_details_bottom_sheet.xml | 4 ++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt index f6010bb53..f13c2824d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.ClipData import android.content.ClipboardManager import android.content.Context +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -14,9 +15,11 @@ import android.widget.Toast import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kotlinx.android.synthetic.main.fragment_user_details_bottom_sheet.* import network.loki.messenger.R +import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient +import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.util.UiModeUtilities @@ -63,11 +66,23 @@ class UserDetailsBottomSheet : BottomSheetDialogFragment() { } nameTextView.text = recipient.name ?: publicKey // Uses the Contact API internally publicKeyTextView.text = publicKey - copyButton.setOnClickListener { + publicKeyTextView.setOnLongClickListener { val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("Session ID", publicKey) clipboard.setPrimaryClip(clip) Toast.makeText(requireContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() + true + } + messageButton.setOnClickListener { + val threadId = MessagingModuleConfiguration.shared.storage.getThreadId(recipient) + val intent = Intent( + context, + ConversationActivityV2::class.java + ) + intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) + intent.putExtra(ConversationActivityV2.THREAD_ID, threadId ?: -1) + startActivity(intent) + dismiss() } } diff --git a/app/src/main/res/layout/fragment_user_details_bottom_sheet.xml b/app/src/main/res/layout/fragment_user_details_bottom_sheet.xml index 985de78a6..4b46e134c 100644 --- a/app/src/main/res/layout/fragment_user_details_bottom_sheet.xml +++ b/app/src/main/res/layout/fragment_user_details_bottom_sheet.xml @@ -111,12 +111,12 @@