From c0a5a6143154c810ce236ed3c0213a544c15915d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 10 Aug 2021 09:36:20 +1000 Subject: [PATCH 01/28] 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 0000000000..a22dd93348 --- /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 7ee8dc9946..390a67a1a8 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 0ca46d3a9f..4dce0ba511 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/28] 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 4da3409d04..d297ba588b 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 7b1592a527..cfbedb7338 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/28] 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 5a13830bd1..93ad37e323 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 31db5b4514..6c1d5784e8 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 94f59c4b43..86534bb9a5 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 99ad38bd5c..0a00892107 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 cfbd5605bd..b514d9672a 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/28] 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 93ad37e323..51835fc88e 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 c36c197cbb..85b345e3f6 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 fe9327a13e..66332bed60 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 106528ee25..a7fa86cb5a 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 bc7324d21a..3c8e0c7123 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 56779795ea..d201daa98d 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 4758525abb..aa78074ed5 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/28] 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 aa78074ed5..0de835076d 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/28] 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 d297ba588b..3bc826970e 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/28] 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 51835fc88e..076a1e5c01 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 4a86d5edc5..e90dc830a3 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 85b345e3f6..07513ec328 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 a7fa86cb5a..ccf5c9e777 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/28] 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 e90dc830a3..7abe4a704d 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/28] 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 0000000000..3adfc125fb --- /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 0000000000..20b6e61043 --- /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/28] 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 3adfc125fb..96091428ed 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 8f2410652d..dea9122a1e 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 9fa0283acc..360bd4bece 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/28] 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 076a1e5c01..99153ae673 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 96091428ed..a91473352c 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 dea9122a1e..558bc13617 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 0fcb61d741..d6dd097646 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 38e9eeccce..597d14f093 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 81ec17bd0e..52642b5d1f 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 c148f8ebcc..eaa289807e 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 3adb9cbda5..10e4cb753e 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 6c1d5784e8..31db5b4514 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 20b6e61043..aed7602591 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/28] 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 99153ae673..aa1fe66f37 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 d6dd097646..f542058c76 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 597d14f093..1fa26000a6 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 42284a4ff0..150f7f4c51 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 eaa289807e..5b75037636 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 c9fa010e8b..3d894a0464 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 86534bb9a5..356307d1dd 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 b514d9672a..1d5a797b77 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/28] 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 15de18a608..59e9f4d6f4 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 ad1864d055..5e5b1da220 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 4f89c336766e8cd3fdb2adbca90c2b5ada6b5203 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 12 Aug 2021 16:37:53 +1000 Subject: [PATCH 14/28] 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 3d894a0464..361d1449e1 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 aad58043d361e034892130f99d12a4c97300c50b Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 13 Aug 2021 15:30:26 +1000 Subject: [PATCH 15/28] add UI options for delete messages --- .../conversation/v2/ConversationActivityV2.kt | 47 ++++++++++++----- .../v2/DeleteOptionsBottomSheet.kt | 52 +++++++++++++++++++ .../fragment_delete_message_bottom_sheet.xml | 29 +++++++++++ 3 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt create mode 100644 app/src/main/res/layout/fragment_delete_message_bottom_sheet.xml 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 7abe4a704d..a6e2426355 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 @@ -1167,22 +1167,41 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } 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) - builder.setPositiveButton(R.string.delete) { _, _ -> - for (message in messages) { - this.deleteForEveryone(message) + if (thread.isOpenGroupRecipient) { + 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) + builder.setPositiveButton(R.string.delete) { _, _ -> + for (message in messages) { + this.deleteForEveryone(message) + } + endActionMode() } - endActionMode() + builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.dismiss() + endActionMode() + } + builder.show() + } else { + val bottomSheet = DeleteOptionsBottomSheet() + bottomSheet.recipient = thread + bottomSheet.onDeleteForMeTapped = { + for (message in messages) { + this.deleteLocally(message) + } + bottomSheet.dismiss() + } + bottomSheet.onDeleteForEveryoneTapped = { + for (message in messages) { + this.deleteForEveryone(message) + } + bottomSheet.dismiss() + } + bottomSheet.onCancelTapped = { bottomSheet.dismiss() } + bottomSheet.show(supportFragmentManager, bottomSheet.tag) } - builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> - dialog.dismiss() - endActionMode() - } - builder.show() } override fun banUser(messages: Set) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt new file mode 100644 index 0000000000..4d84f7c0f9 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt @@ -0,0 +1,52 @@ +package org.thoughtcrime.securesms.conversation.v2 + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlinx.android.synthetic.main.fragment_conversation_bottom_sheet.* +import kotlinx.android.synthetic.main.fragment_delete_message_bottom_sheet.* +import network.loki.messenger.R +import org.session.libsession.utilities.recipients.Recipient +import org.thoughtcrime.securesms.util.UiModeUtilities + +class DeleteOptionsBottomSheet: BottomSheetDialogFragment(), View.OnClickListener { + + lateinit var recipient: Recipient + + var onDeleteForMeTapped: (() -> Unit?)? = null + var onDeleteForEveryoneTapped: (() -> Unit)? = null + var onCancelTapped: (() -> Unit)? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_delete_message_bottom_sheet, container, false) + } + + override fun onClick(v: View?) { + when (v) { + deleteForMeTextView -> onDeleteForMeTapped?.invoke() + deleteForEveryoneTextView -> onDeleteForEveryoneTapped?.invoke() + cancelTextView -> onCancelTapped?.invoke() + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + if (!this::recipient.isInitialized) { return dismiss() } + if (!recipient.isGroupRecipient) { + deleteForEveryoneTextView.text = resources.getString(R.string.delete_message_for_me_and_recipient, recipient.name) + } + deleteForMeTextView.setOnClickListener(this) + deleteForEveryoneTextView.setOnClickListener(this) + cancelTextView.setOnClickListener(this) + } + + override fun onStart() { + super.onStart() + val window = dialog?.window ?: return + val isLightMode = UiModeUtilities.isDayUiMode(requireContext()) + window.setDimAmount(if (isLightMode) 0.1f else 0.75f) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_delete_message_bottom_sheet.xml b/app/src/main/res/layout/fragment_delete_message_bottom_sheet.xml new file mode 100644 index 0000000000..592701ae08 --- /dev/null +++ b/app/src/main/res/layout/fragment_delete_message_bottom_sheet.xml @@ -0,0 +1,29 @@ + + + + + + + + + + From cb59a1af2d5a22607a965ee74429594eef0f80a8 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 13 Aug 2021 15:49:05 +1000 Subject: [PATCH 16/28] minor fix --- .../conversation/v2/ConversationActivityV2.kt | 12 ++++++++++-- 1 file changed, 10 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 a6e2426355..1ac56afd9b 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 @@ -1155,7 +1155,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } } else { - messageDataProvider.getServerHashForMessage(message.id)?.let { serverHash -> + val serverHash = messageDataProvider.getServerHashForMessage(message.id) + if (serverHash == null) { + messageDataProvider.deleteMessage(message.id, !message.isMms) + } else { SnodeAPI.deleteMessage(thread.address.serialize(), listOf(serverHash)) .success { messageDataProvider.deleteMessage(message.id, !message.isMms) @@ -1192,14 +1195,19 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe this.deleteLocally(message) } bottomSheet.dismiss() + endActionMode() } bottomSheet.onDeleteForEveryoneTapped = { for (message in messages) { this.deleteForEveryone(message) } bottomSheet.dismiss() + endActionMode() + } + bottomSheet.onCancelTapped = { + bottomSheet.dismiss() + endActionMode() } - bottomSheet.onCancelTapped = { bottomSheet.dismiss() } bottomSheet.show(supportFragmentManager, bottomSheet.tag) } } From 064e5099924af0b4a67c8e3f28bbd123c00b084f Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 13 Aug 2021 16:33:30 +1000 Subject: [PATCH 17/28] fix a minor issue for closed group --- .../conversation/v2/ConversationActivityV2.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 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 1ac56afd9b..234ee65c3c 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 @@ -63,6 +63,7 @@ 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.GroupUtil import org.session.libsession.utilities.MediaTypes import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.concurrent.SimpleTask @@ -72,6 +73,7 @@ import org.session.libsignal.crypto.MnemonicCodec import org.session.libsignal.utilities.ListenableFuture import org.session.libsignal.utilities.guava.Optional import org.session.libsignal.utilities.hexEncodedPrivateKey +import org.session.libsignal.utilities.toHexString import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.audio.AudioRecorder @@ -1155,14 +1157,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } } else { - val serverHash = messageDataProvider.getServerHashForMessage(message.id) - if (serverHash == null) { - messageDataProvider.deleteMessage(message.id, !message.isMms) - } else { - SnodeAPI.deleteMessage(thread.address.serialize(), listOf(serverHash)) - .success { - messageDataProvider.deleteMessage(message.id, !message.isMms) - }.failUi { error -> + messageDataProvider.deleteMessage(message.id, !message.isMms) + messageDataProvider.getServerHashForMessage(message.id)?.let { serverHash -> + var publicKey = thread.address.serialize() + if (thread.isClosedGroupRecipient) { publicKey = GroupUtil.doubleDecodeGroupID(publicKey).toHexString() } + SnodeAPI.deleteMessage(publicKey, listOf(serverHash)) + .failUi { error -> Toast.makeText(this@ConversationActivityV2, "Couldn't delete message due to error: $error", Toast.LENGTH_LONG).show() } } From ee3e9ae17fcbea73d42c6c8e35001f01cd9be9a2 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 16 Aug 2021 09:58:28 +1000 Subject: [PATCH 18/28] delete outgoing message for linked device --- .../attachments/DatabaseAttachmentProvider.kt | 23 ++++++++----------- .../securesms/database/MessagingDatabase.java | 2 ++ .../securesms/database/MmsDatabase.java | 7 +++--- .../securesms/database/SmsDatabase.java | 1 + .../service/ExpiringMessageManager.java | 2 +- .../securesms/util/AttachmentUtil.java | 2 +- 6 files changed, 18 insertions(+), 19 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 aa1fe66f37..2ed700cb3d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -19,6 +19,7 @@ import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.database.AttachmentDatabase import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.database.MessagingDatabase import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.events.PartProgressEvent import org.thoughtcrime.securesms.mms.MediaConstraints @@ -167,13 +168,9 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) } override fun deleteMessage(messageID: Long, isSms: Boolean) { - if (isSms) { - val db = DatabaseFactory.getSmsDatabase(context) - db.deleteMessage(messageID) - } else { - val db = DatabaseFactory.getMmsDatabase(context) - db.delete(messageID) - } + val messagingDatabase: MessagingDatabase = if (isSms) DatabaseFactory.getSmsDatabase(context) + else DatabaseFactory.getMmsDatabase(context) + messagingDatabase.deleteMessage(messageID) DatabaseFactory.getLokiMessageDatabase(context).deleteMessage(messageID, isSms) DatabaseFactory.getLokiMessageDatabase(context).deleteMessageServerHash(messageID) } @@ -182,14 +179,12 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) 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) + val messagingDatabase: MessagingDatabase = if (message.isMms) DatabaseFactory.getMmsDatabase(context) + else DatabaseFactory.getSmsDatabase(context) + messagingDatabase.markAsDeleted(message.id, message.isRead) + if (message.isOutgoing) { + messagingDatabase.deleteMessage(message.id) } - } 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 f542058c76..a3906d2871 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java @@ -40,6 +40,8 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn public abstract void markAsDeleted(long messageId, boolean read); + public abstract boolean deleteMessage(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 1fa26000a6..f56792aa71 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -923,7 +923,8 @@ public class MmsDatabase extends MessagingDatabase { reader.close(); } - public boolean delete(long messageId) { + @Override + public boolean deleteMessage(long messageId) { long threadId = getThreadIdForMessage(messageId); AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context); ThreadUtils.queue(() -> attachmentDatabase.deleteAttachmentsForMessage(messageId)); @@ -1050,7 +1051,7 @@ public class MmsDatabase extends MessagingDatabase { cursor = db.query(TABLE_NAME, new String[] {ID}, where, null, null, null, null); while (cursor != null && cursor.moveToNext()) { - delete(cursor.getLong(0)); + deleteMessage(cursor.getLong(0)); } } finally { @@ -1076,7 +1077,7 @@ public class MmsDatabase extends MessagingDatabase { while (cursor != null && cursor.moveToNext()) { Log.i("MmsDatabase", "Trimming: " + cursor.getLong(0)); - delete(cursor.getLong(0)); + deleteMessage(cursor.getLong(0)); } } finally { 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 5b75037636..17a8364776 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -529,6 +529,7 @@ public class SmsDatabase extends MessagingDatabase { return cursor; } + @Override public boolean deleteMessage(long messageId) { Log.i("MessageDatabase", "Deleting: " + messageId); SQLiteDatabase db = databaseHelper.getWritableDatabase(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java index c2c4834cd5..59cd5cc0c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -229,7 +229,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM } if (expiredMessage != null) { - if (expiredMessage.mms) mmsDatabase.delete(expiredMessage.id); + if (expiredMessage.mms) mmsDatabase.deleteMessage(expiredMessage.id); else smsDatabase.deleteMessage(expiredMessage.id); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AttachmentUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/AttachmentUtil.java index 13cdcc6d6a..2697a634cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/AttachmentUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AttachmentUtil.java @@ -66,7 +66,7 @@ public class AttachmentUtil { .size(); if (attachmentCount <= 1) { - DatabaseFactory.getMmsDatabase(context).delete(mmsId); + DatabaseFactory.getMmsDatabase(context).deleteMessage(mmsId); } else { DatabaseFactory.getAttachmentDatabase(context).deleteAttachment(attachmentId); } From f10ddb8e3a7cdc79f36b99aa8e93eb5d237231ce Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 16 Aug 2021 10:34:28 +1000 Subject: [PATCH 19/28] cancel notification for unsend request --- .../messaging/sending_receiving/ReceivedMessageHandler.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 1d5a797b77..82ac2139d2 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,7 +157,8 @@ fun MessageReceiver.handleUnsendRequest(message: UnsendRequest) { val author = message.author ?: return val messageIdToDelete = storage.getMessageIdInDatabase(timestamp, author) ?: return if (!messageDataProvider.isOutgoingMessage(messageIdToDelete)) { - // TODO: Cancel the notification of this message + // FIXME: Not sure if there is any performance issue here + SSKEnvironment.shared.notificationManager.updateNotification(context) } messageDataProvider.getServerHashForMessage(messageIdToDelete)?.let { serverHash -> SnodeAPI.deleteMessage(author, listOf(serverHash)) From feff1c83938b5b921d0aabe122907d8850287500 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 16 Aug 2021 10:58:07 +1000 Subject: [PATCH 20/28] fix cancel notification --- .../messaging/sending_receiving/ReceivedMessageHandler.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 82ac2139d2..dbea3687f7 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 @@ -156,14 +156,14 @@ fun MessageReceiver.handleUnsendRequest(message: UnsendRequest) { val timestamp = message.timestamp ?: return val author = message.author ?: return val messageIdToDelete = storage.getMessageIdInDatabase(timestamp, author) ?: return - if (!messageDataProvider.isOutgoingMessage(messageIdToDelete)) { - // FIXME: Not sure if there is any performance issue here - SSKEnvironment.shared.notificationManager.updateNotification(context) - } messageDataProvider.getServerHashForMessage(messageIdToDelete)?.let { serverHash -> SnodeAPI.deleteMessage(author, listOf(serverHash)) } messageDataProvider.updateMessageAsDeleted(timestamp, author) + if (!messageDataProvider.isOutgoingMessage(messageIdToDelete)) { + // FIXME: Not sure if there is any performance issue here + SSKEnvironment.shared.notificationManager.updateNotification(context) + } } //endregion From 4ce1bd6875cd7c4fdc9cf4dd01d233dbb8aef4c9 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 16 Aug 2021 11:59:52 +1000 Subject: [PATCH 21/28] clean --- .../org/thoughtcrime/securesms/database/MmsSmsDatabase.java | 4 +--- .../messaging/sending_receiving/ReceivedMessageHandler.kt | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) 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 150f7f4c51..6ba77ce83f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -128,9 +128,7 @@ public class MmsSmsDatabase extends Database { public Cursor getConversationSnippet(long threadId) { String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; - - // FIXME: Not sure if this will cause any performance issues - // return queryTables(PROJECTION, selection, order, "1"); + return queryTables(PROJECTION, selection, order, null); } 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 dbea3687f7..de2b8a349c 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 @@ -161,7 +161,6 @@ fun MessageReceiver.handleUnsendRequest(message: UnsendRequest) { } messageDataProvider.updateMessageAsDeleted(timestamp, author) if (!messageDataProvider.isOutgoingMessage(messageIdToDelete)) { - // FIXME: Not sure if there is any performance issue here SSKEnvironment.shared.notificationManager.updateNotification(context) } } From e8551a354534f59c02ca25f35aaec8fbab3d9ea8 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 16 Aug 2021 12:08:35 +1000 Subject: [PATCH 22/28] improve delete option ui --- .../conversation/v2/ConversationActivityV2.kt | 20 ++++++++++++++++++- .../menus/ConversationActionModeCallback.kt | 3 ++- .../securesms/database/MmsSmsDatabase.java | 2 +- 3 files changed, 22 insertions(+), 3 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 234ee65c3c..a4e50fb6c6 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 @@ -1170,6 +1170,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun deleteMessages(messages: Set) { + val allSentByCurrentUser = messages.all { it.isOutgoing } if (thread.isOpenGroupRecipient) { val messageCount = messages.size val builder = AlertDialog.Builder(this) @@ -1187,7 +1188,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe endActionMode() } builder.show() - } else { + } else if (allSentByCurrentUser) { val bottomSheet = DeleteOptionsBottomSheet() bottomSheet.recipient = thread bottomSheet.onDeleteForMeTapped = { @@ -1209,6 +1210,23 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe endActionMode() } bottomSheet.show(supportFragmentManager, bottomSheet.tag) + } else { + 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) + builder.setPositiveButton(R.string.delete) { _, _ -> + for (message in messages) { + this.deleteLocally(message) + } + endActionMode() + } + builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.dismiss() + endActionMode() + } + builder.show() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt index 2372657751..e1bde23e44 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt @@ -34,8 +34,9 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p val thread = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadID)!! val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! fun userCanDeleteSelectedItems(): Boolean { - if (openGroup == null) { return true } val allSentByCurrentUser = selectedItems.all { it.isOutgoing } + val allReceivedByByCurrentUser = selectedItems.all { !it.isOutgoing } + if (openGroup == null) { return allSentByCurrentUser || allReceivedByByCurrentUser } if (allSentByCurrentUser) { return true } return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server) } 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 6ba77ce83f..86b5231643 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -128,7 +128,7 @@ public class MmsSmsDatabase extends Database { public Cursor getConversationSnippet(long threadId) { String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; - + return queryTables(PROJECTION, selection, order, null); } From 29e355c6abef244a33c4625b0453e791ff13b05f Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 16 Aug 2021 14:03:06 +1000 Subject: [PATCH 23/28] send unsend request as sync message --- .../sending_receiving/MessageSender.kt | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 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 0de835076d..1cb041e915 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 @@ -254,34 +254,35 @@ object MessageSender { fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) { val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! - val messageID = storage.getMessageIdInDatabase(message.sentTimestamp!!, message.sender?:userPublicKey) ?: return // Ignore future self-sends storage.addReceivedMessageTimestamp(message.sentTimestamp!!) - if (openGroupSentTimestamp != -1L && message is VisibleMessage) { - storage.addReceivedMessageTimestamp(openGroupSentTimestamp) - 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()) - val threadID = storage.getThreadId(Address.fromSerialized(encoded)) - if (threadID != null && threadID >= 0) { - storage.setOpenGroupServerMessageID(messageID, message.openGroupServerMessageID!!, threadID, !(message as VisibleMessage).isMediaMessage()) + storage.getMessageIdInDatabase(message.sentTimestamp!!, message.sender?:userPublicKey)?.let { messageID -> + if (openGroupSentTimestamp != -1L && message is VisibleMessage) { + storage.addReceivedMessageTimestamp(openGroupSentTimestamp) + 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()) + val threadID = storage.getThreadId(Address.fromSerialized(encoded)) + if (threadID != null && threadID >= 0) { + storage.setOpenGroupServerMessageID(messageID, message.openGroupServerMessageID!!, threadID, !(message as VisibleMessage).isMediaMessage()) + } + } + // Mark the message as sent + storage.markAsSent(message.sentTimestamp!!, message.sender?:userPublicKey) + storage.markUnidentified(message.sentTimestamp!!, message.sender?:userPublicKey) + // Start the disappearing messages timer if needed + if (message is VisibleMessage && !isSyncMessage) { + SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message.sentTimestamp!!, message.sender?:userPublicKey) } - } - // Mark the message as sent - storage.markAsSent(message.sentTimestamp!!, message.sender?:userPublicKey) - storage.markUnidentified(message.sentTimestamp!!, message.sender?:userPublicKey) - // Start the disappearing messages timer if needed - if (message is VisibleMessage && !isSyncMessage) { - SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message.sentTimestamp!!, message.sender?:userPublicKey) } // Sync the message if: // • it's a visible message From b9dd22920743ee8b182235f4bf1fdf4b86fc561e Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 16 Aug 2021 15:09:12 +1000 Subject: [PATCH 24/28] add a flag to disable unsend requests --- .../conversation/v2/ConversationActivityV2.kt | 50 +++++++++++++++++++ .../menus/ConversationActionModeCallback.kt | 9 ++++ 2 files changed, 59 insertions(+) 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 a4e50fb6c6..bff0a21213 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 @@ -209,6 +209,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe const val PICK_GIF = 10 const val PICK_FROM_LIBRARY = 12 const val INVITE_CONTACTS = 124 + + //flag + val isUnsendRequestsEnabled = false } // endregion @@ -1169,7 +1172,54 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } + // Remove this after the unsend request is enabled + fun deleteMessagesWithoutUnsendRequest(messages: Set) { + val messageCount = messages.size + val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider + val messageDB = DatabaseFactory.getLokiMessageDatabase(this@ConversationActivityV2) + 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).deleteMessage(message.id) + } else { + DatabaseFactory.getSmsDatabase(this@ConversationActivityV2).deleteMessage(message.id) + } + } + } + endActionMode() + } + builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.dismiss() + endActionMode() + } + builder.show() + } + override fun deleteMessages(messages: Set) { + if (!isUnsendRequestsEnabled) { + deleteMessagesWithoutUnsendRequest(messages) + return + } val allSentByCurrentUser = messages.all { it.isOutgoing } if (thread.isOpenGroupRecipient) { val messageCount = messages.size diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt index e1bde23e44..f9603f935f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt @@ -7,6 +7,7 @@ import android.view.MenuItem import network.loki.messenger.R import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.utilities.TextSecurePreferences +import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.ConversationAdapter import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord @@ -35,6 +36,14 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! fun userCanDeleteSelectedItems(): Boolean { val allSentByCurrentUser = selectedItems.all { it.isOutgoing } + + // Remove this after the unsend request is enabled + if (!ConversationActivityV2.isUnsendRequestsEnabled) { + if (openGroup == null) { return true } + if (allSentByCurrentUser) { return true } + return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server) + } + val allReceivedByByCurrentUser = selectedItems.all { !it.isOutgoing } if (openGroup == null) { return allSentByCurrentUser || allReceivedByByCurrentUser } if (allSentByCurrentUser) { return true } From c4a346341694a9e99eac82125153dadee7967a5a Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 17 Aug 2021 12:17:40 +1000 Subject: [PATCH 25/28] send unsend request to self only for delete locally --- .../securesms/conversation/v2/ConversationActivityV2.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 bff0a21213..0d37e0ac54 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 @@ -1138,7 +1138,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private fun deleteLocally(message: MessageRecord) { buildUsendRequest(message)?.let { unsendRequest -> - MessageSender.send(unsendRequest, thread.address) + TextSecurePreferences.getLocalNumber(this)?.let { + MessageSender.send(unsendRequest, Address.fromSerialized(it)) + } } MessagingModuleConfiguration.shared.messageDataProvider.deleteMessage(message.id, !message.isMms) } From ecc881bc7cb758a38e34b2f2ca6db6e220847fdf Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 17 Aug 2021 14:34:49 +1000 Subject: [PATCH 26/28] store server hash value for incoming messages --- .../securesms/conversation/v2/ConversationActivityV2.kt | 2 +- .../java/org/thoughtcrime/securesms/database/Storage.kt | 5 +++++ .../securesms/notifications/BackgroundPollWorker.kt | 4 ++-- .../libsession/messaging/jobs/MessageReceiveJob.kt | 6 +++++- .../sending_receiving/pollers/ClosedGroupPollerV2.kt | 4 ++-- .../messaging/sending_receiving/pollers/Poller.kt | 4 ++-- .../main/java/org/session/libsession/snode/SnodeAPI.kt | 8 ++++---- 7 files changed, 21 insertions(+), 12 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 0d37e0ac54..fec9dcc558 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 @@ -1138,7 +1138,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private fun deleteLocally(message: MessageRecord) { buildUsendRequest(message)?.let { unsendRequest -> - TextSecurePreferences.getLocalNumber(this)?.let { + TextSecurePreferences.getLocalNumber(this@ConversationActivityV2)?.let { MessageSender.send(unsendRequest, Address.fromSerialized(it)) } } 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 66332bed60..950b6d8238 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -148,6 +148,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, if (openGroupID.isNullOrEmpty() && threadID != null && threadID >= 0) { JobQueue.shared.add(TrimThreadJob(threadID)) } + message.serverHash?.let { serverHash -> + messageID?.let { id -> + DatabaseFactory.getLokiMessageDatabase(context).setMessageServerHash(id, serverHash) + } + } return messageID } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt index afd4ad74a8..d6b1267528 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt @@ -49,9 +49,9 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor // DMs val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! val dmsPromise = SnodeAPI.getMessages(userPublicKey).map { envelopes -> - envelopes.map { envelope -> + envelopes.map { (envelope, serverHash) -> // FIXME: Using a job here seems like a bad idea... - MessageReceiveJob(envelope.toByteArray()).executeAsync() + MessageReceiveJob(envelope.toByteArray(), serverHash).executeAsync() } } promises.addAll(dmsPromise.get()) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt index 923e48a83f..0601c670af 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt @@ -7,7 +7,7 @@ import org.session.libsession.messaging.sending_receiving.handle import org.session.libsession.messaging.utilities.Data import org.session.libsignal.utilities.Log -class MessageReceiveJob(val data: ByteArray, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job { +class MessageReceiveJob(val data: ByteArray, val serverHash: String? = null, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job { override var delegate: JobDelegate? = null override var id: String? = null override var failureCount: Int = 0 @@ -21,6 +21,7 @@ class MessageReceiveJob(val data: ByteArray, val openGroupMessageServerID: Long? // Keys used for database storage private val DATA_KEY = "data" + private val SERVER_HASH_KEY = "serverHash" private val OPEN_GROUP_MESSAGE_SERVER_ID_KEY = "openGroupMessageServerID" private val OPEN_GROUP_ID_KEY = "open_group_id" } @@ -34,6 +35,7 @@ class MessageReceiveJob(val data: ByteArray, val openGroupMessageServerID: Long? try { val isRetry: Boolean = failureCount != 0 val (message, proto) = MessageReceiver.parse(this.data, this.openGroupMessageServerID) + message.serverHash = serverHash synchronized(RECEIVE_LOCK) { // FIXME: Do we need this? MessageReceiver.handle(message, proto, this.openGroupID) } @@ -67,6 +69,7 @@ class MessageReceiveJob(val data: ByteArray, val openGroupMessageServerID: Long? override fun serialize(): Data { val builder = Data.Builder().putByteArray(DATA_KEY, data) + serverHash?.let { builder.putString(SERVER_HASH_KEY, it) } openGroupMessageServerID?.let { builder.putLong(OPEN_GROUP_MESSAGE_SERVER_ID_KEY, it) } openGroupID?.let { builder.putString(OPEN_GROUP_ID_KEY, it) } return builder.build(); @@ -81,6 +84,7 @@ class MessageReceiveJob(val data: ByteArray, val openGroupMessageServerID: Long? override fun create(data: Data): MessageReceiveJob { return MessageReceiveJob( data.getByteArray(DATA_KEY), + data.getString(SERVER_HASH_KEY), data.getLong(OPEN_GROUP_MESSAGE_SERVER_ID_KEY), data.getString(OPEN_GROUP_ID_KEY) ) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPollerV2.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPollerV2.kt index 62e39b40f1..f165c97540 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPollerV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPollerV2.kt @@ -102,8 +102,8 @@ class ClosedGroupPollerV2 { } promise.success { envelopes -> if (!isPolling(groupPublicKey)) { return@success } - envelopes.forEach { envelope -> - val job = MessageReceiveJob(envelope.toByteArray()) + envelopes.forEach { (envelope, serverHash) -> + val job = MessageReceiveJob(envelope.toByteArray(), serverHash) JobQueue.shared.add(job) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt index bbf2620f97..be472874d0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt @@ -91,8 +91,8 @@ class Poller { task { Unit } // The long polling connection has been canceled; don't recurse } else { val messages = SnodeAPI.parseRawMessagesResponse(rawResponse, snode, userPublicKey) - messages.forEach { envelope -> - val job = MessageReceiveJob(envelope.toByteArray()) + messages.forEach { (envelope, serverHash) -> + val job = MessageReceiveJob(envelope.toByteArray(), serverHash) JobQueue.shared.add(job) } poll(snode, deferred) 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 3bc826970e..dfbc6c34da 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -426,7 +426,7 @@ object SnodeAPI { } } - fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String): List { + fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String): List> { val messages = rawResponse["messages"] as? List<*> return if (messages != null) { updateLastMessageHashValueIfPossible(snode, publicKey, messages) @@ -465,14 +465,14 @@ object SnodeAPI { return result } - private fun parseEnvelopes(rawMessages: List<*>): List { + private fun parseEnvelopes(rawMessages: List<*>): List> { return rawMessages.mapNotNull { rawMessage -> val rawMessageAsJSON = rawMessage as? Map<*, *> val base64EncodedData = rawMessageAsJSON?.get("data") as? String val data = base64EncodedData?.let { Base64.decode(it) } if (data != null) { try { - MessageWrapper.unwrap(data) + Pair(MessageWrapper.unwrap(data), rawMessageAsJSON.get("hash") as? String) } catch (e: Exception) { Log.d("Loki", "Failed to unwrap data for message: ${rawMessage.prettifiedDescription()}.") null @@ -568,5 +568,5 @@ object SnodeAPI { // Type Aliases typealias RawResponse = Map<*, *> -typealias MessageListPromise = Promise, Exception> +typealias MessageListPromise = Promise>, Exception> typealias RawResponsePromise = Promise From d9eb2c75852f1ddbd4c9c9bf0a738e82dc54555a Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 17 Aug 2021 15:11:53 +1000 Subject: [PATCH 27/28] don't show delete options for old messages --- .../securesms/conversation/v2/ConversationActivityV2.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 fec9dcc558..f8772bff45 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 @@ -1223,6 +1223,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe return } val allSentByCurrentUser = messages.all { it.isOutgoing } + val allHasHash = messages.all { DatabaseFactory.getLokiMessageDatabase(this@ConversationActivityV2).getMessageServerHash(it.id) != null } if (thread.isOpenGroupRecipient) { val messageCount = messages.size val builder = AlertDialog.Builder(this) @@ -1240,7 +1241,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe endActionMode() } builder.show() - } else if (allSentByCurrentUser) { + } else if (allSentByCurrentUser && allHasHash) { val bottomSheet = DeleteOptionsBottomSheet() bottomSheet.recipient = thread bottomSheet.onDeleteForMeTapped = { From b03ba5bfa7ac3c122ac399f59b276a20e59b23c6 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 17 Aug 2021 16:16:17 +1000 Subject: [PATCH 28/28] clean --- .../attachments/DatabaseAttachmentProvider.kt | 2 +- .../conversation/v2/ConversationActivityV2.kt | 10 +++++----- .../v2/menus/ConversationActionModeCallback.kt | 6 +++--- .../layout/fragment_delete_message_bottom_sheet.xml | 4 ++-- 4 files changed, 11 insertions(+), 11 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 2ed700cb3d..e2f78eecc7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -178,7 +178,7 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) override fun updateMessageAsDeleted(timestamp: Long, author: String) { val database = DatabaseFactory.getMmsSmsDatabase(context) val address = Address.fromSerialized(author) - val message = database.getMessageFor(timestamp, address)!! + val message = database.getMessageFor(timestamp, address) ?: return val messagingDatabase: MessagingDatabase = if (message.isMms) DatabaseFactory.getMmsDatabase(context) else DatabaseFactory.getSmsDatabase(context) messagingDatabase.markAsDeleted(message.id, message.isRead) 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 f8772bff45..b5aa6a1ae3 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 @@ -211,7 +211,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe const val INVITE_CONTACTS = 124 //flag - val isUnsendRequestsEnabled = false + const val IS_UNSEND_REQUESTS_ENABLED = false } // endregion @@ -1121,7 +1121,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe stopAudioHandler.removeCallbacks(stopVoiceMessageRecordingTask) } - private fun buildUsendRequest(message: MessageRecord): UnsendRequest? { + private fun buildUnsendRequest(message: MessageRecord): UnsendRequest? { if (this.thread.isOpenGroupRecipient) return null val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider messageDataProvider.getServerHashForMessage(message.id) ?: return null @@ -1137,7 +1137,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } private fun deleteLocally(message: MessageRecord) { - buildUsendRequest(message)?.let { unsendRequest -> + buildUnsendRequest(message)?.let { unsendRequest -> TextSecurePreferences.getLocalNumber(this@ConversationActivityV2)?.let { MessageSender.send(unsendRequest, Address.fromSerialized(it)) } @@ -1146,7 +1146,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } private fun deleteForEveryone(message: MessageRecord) { - buildUsendRequest(message)?.let { unsendRequest -> + buildUnsendRequest(message)?.let { unsendRequest -> MessageSender.send(unsendRequest, thread.address) } val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider @@ -1218,7 +1218,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun deleteMessages(messages: Set) { - if (!isUnsendRequestsEnabled) { + if (!IS_UNSEND_REQUESTS_ENABLED) { deleteMessagesWithoutUnsendRequest(messages) return } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt index f9603f935f..48772f8adf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt @@ -38,14 +38,14 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p val allSentByCurrentUser = selectedItems.all { it.isOutgoing } // Remove this after the unsend request is enabled - if (!ConversationActivityV2.isUnsendRequestsEnabled) { + if (!ConversationActivityV2.IS_UNSEND_REQUESTS_ENABLED) { if (openGroup == null) { return true } if (allSentByCurrentUser) { return true } return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server) } - val allReceivedByByCurrentUser = selectedItems.all { !it.isOutgoing } - if (openGroup == null) { return allSentByCurrentUser || allReceivedByByCurrentUser } + val allReceivedByCurrentUser = selectedItems.all { !it.isOutgoing } + if (openGroup == null) { return allSentByCurrentUser || allReceivedByCurrentUser } if (allSentByCurrentUser) { return true } return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server) } diff --git a/app/src/main/res/layout/fragment_delete_message_bottom_sheet.xml b/app/src/main/res/layout/fragment_delete_message_bottom_sheet.xml index 592701ae08..031813a7c5 100644 --- a/app/src/main/res/layout/fragment_delete_message_bottom_sheet.xml +++ b/app/src/main/res/layout/fragment_delete_message_bottom_sheet.xml @@ -13,13 +13,13 @@ android:id="@+id/deleteForMeTextView" style="@style/BottomSheetActionItem" android:text="@string/delete_message_for_me" - android:textColor="@color/core_red"/> + android:textColor="@color/destructive"/> + android:textColor="@color/destructive"/>