diff --git a/build.gradle b/build.gradle
index cd53100e3c..946d118de2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -79,7 +79,6 @@ task qa {
':Signal-Android:lintPlayProdRelease',
'Signal-Android:ktlintCheck',
':libsignal-service:test',
- ':libsignal-service:ktlintCheck',
':Signal-Android:assemblePlayProdRelease'
}
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index fc25766bbd..b6a80016e0 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -2292,14 +2292,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
-
-
-
@@ -2308,14 +2300,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
-
-
-
@@ -2324,14 +2308,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
-
-
-
@@ -2340,14 +2316,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
-
-
-
@@ -2356,14 +2324,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
-
-
-
@@ -2372,14 +2332,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
-
-
-
@@ -2388,14 +2340,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
-
-
-
@@ -2404,14 +2348,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
-
-
-
@@ -2420,14 +2356,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
-
-
-
@@ -2436,14 +2364,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
-
-
-
@@ -2452,14 +2372,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
-
-
-
@@ -3512,11 +3424,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
@@ -3552,11 +3459,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
@@ -3697,11 +3599,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
@@ -3722,11 +3619,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
@@ -3807,11 +3699,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
@@ -3862,11 +3749,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
@@ -3902,11 +3784,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
@@ -3947,11 +3824,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
-
-
diff --git a/libsignal/service/build.gradle b/libsignal/service/build.gradle
index 5d99b0ef21..2216625f7d 100644
--- a/libsignal/service/build.gradle
+++ b/libsignal/service/build.gradle
@@ -1,11 +1,9 @@
apply plugin: 'java-library'
-apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'java-test-fixtures'
apply plugin: 'com.google.protobuf'
apply plugin: 'maven-publish'
apply plugin: 'signing'
apply plugin: 'idea'
-apply plugin: 'org.jlleitschuh.gradle.ktlint'
sourceCompatibility = 1.8
archivesBaseName = "signal-service-java"
@@ -43,8 +41,6 @@ dependencies {
api libs.rxjava3.rxjava
- implementation libs.kotlin.stdlib.jdk8
-
testImplementation testLibs.junit.junit
testImplementation testLibs.assertj.core
testImplementation testLibs.conscrypt.openjdk.uber
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java
index c59f66e2c8..6cdda86785 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java
@@ -721,29 +721,27 @@ public final class SignalServiceContent {
metadata.getSenderDevice());
}
- return SignalServiceDataMessage.newBuilder()
- .withTimestamp(metadata.getTimestamp())
- .asGroupMessage(groupInfoV2)
- .withAttachments(attachments)
- .withBody(content.hasBody() ? content.getBody() : null)
- .asEndSessionMessage(endSession)
- .withExpiration(content.getExpireTimer())
- .asExpirationUpdate(expirationUpdate)
- .withProfileKey(content.hasProfileKey() ? content.getProfileKey().toByteArray() : null)
- .asProfileKeyUpdate(profileKeyUpdate)
- .withQuote(quote)
- .withSharedContacts(sharedContacts)
- .withPreviews(previews)
- .withMentions(mentions)
- .withSticker(sticker)
- .withViewOnce(content.getIsViewOnce())
- .withReaction(reaction)
- .withRemoteDelete(remoteDelete)
- .withGroupCallUpdate(groupCallUpdate)
- .withPayment(payment)
- .withStoryContext(storyContext)
- .withGiftBadge(giftBadge)
- .build();
+ return new SignalServiceDataMessage(metadata.getTimestamp(),
+ groupInfoV2,
+ attachments,
+ content.hasBody() ? content.getBody() : null,
+ endSession,
+ content.getExpireTimer(),
+ expirationUpdate,
+ content.hasProfileKey() ? content.getProfileKey().toByteArray() : null,
+ profileKeyUpdate,
+ quote,
+ sharedContacts,
+ previews,
+ mentions,
+ sticker,
+ content.getIsViewOnce(),
+ reaction,
+ remoteDelete,
+ groupCallUpdate,
+ payment,
+ storyContext,
+ giftBadge);
}
private static SignalServiceSyncMessage createSynchronizeMessage(SignalServiceMetadata metadata,
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java
new file mode 100644
index 0000000000..3c7b0273fa
--- /dev/null
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java
@@ -0,0 +1,729 @@
+/*
+ * Copyright (C) 2014-2016 Open Whisper Systems
+ *
+ * Licensed according to the LICENSE file in this repository.
+ */
+
+package org.whispersystems.signalservice.api.messages;
+
+import org.signal.libsignal.protocol.InvalidMessageException;
+import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
+import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation;
+import org.whispersystems.signalservice.api.messages.shared.SharedContact;
+import org.whispersystems.signalservice.api.push.ServiceId;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.util.OptionalUtil;
+import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Represents a decrypted Signal Service data message.
+ */
+public class SignalServiceDataMessage {
+
+ private final long timestamp;
+ private final Optional> attachments;
+ private final Optional body;
+ private final Optional group;
+ private final Optional profileKey;
+ private final boolean endSession;
+ private final boolean expirationUpdate;
+ private final int expiresInSeconds;
+ private final boolean profileKeyUpdate;
+ private final Optional quote;
+ private final Optional> contacts;
+ private final Optional> previews;
+ private final Optional> mentions;
+ private final Optional sticker;
+ private final boolean viewOnce;
+ private final Optional reaction;
+ private final Optional remoteDelete;
+ private final Optional groupCallUpdate;
+ private final Optional payment;
+ private final Optional storyContext;
+ private final Optional giftBadge;
+
+ /**
+ * Construct a SignalServiceDataMessage.
+ *
+ * @param timestamp The sent timestamp.
+ * @param groupV2 The group information (or null if none).
+ * @param attachments The attachments (or null if none).
+ * @param body The message contents.
+ * @param endSession Flag indicating whether this message should close a session.
+ * @param expiresInSeconds Number of seconds in which the message should disappear after being seen.
+ */
+ SignalServiceDataMessage(long timestamp,
+ SignalServiceGroupV2 groupV2,
+ List attachments,
+ String body,
+ boolean endSession,
+ int expiresInSeconds,
+ boolean expirationUpdate,
+ byte[] profileKey,
+ boolean profileKeyUpdate,
+ Quote quote,
+ List sharedContacts,
+ List previews,
+ List mentions,
+ Sticker sticker,
+ boolean viewOnce,
+ Reaction reaction,
+ RemoteDelete remoteDelete,
+ GroupCallUpdate groupCallUpdate,
+ Payment payment,
+ StoryContext storyContext,
+ GiftBadge giftBadge)
+ {
+ this.group = Optional.ofNullable(groupV2);
+ this.timestamp = timestamp;
+ this.body = OptionalUtil.absentIfEmpty(body);
+ this.endSession = endSession;
+ this.expiresInSeconds = expiresInSeconds;
+ this.expirationUpdate = expirationUpdate;
+ this.profileKey = Optional.ofNullable(profileKey);
+ this.profileKeyUpdate = profileKeyUpdate;
+ this.quote = Optional.ofNullable(quote);
+ this.sticker = Optional.ofNullable(sticker);
+ this.viewOnce = viewOnce;
+ this.reaction = Optional.ofNullable(reaction);
+ this.remoteDelete = Optional.ofNullable(remoteDelete);
+ this.groupCallUpdate = Optional.ofNullable(groupCallUpdate);
+ this.payment = Optional.ofNullable(payment);
+ this.storyContext = Optional.ofNullable(storyContext);
+ this.giftBadge = Optional.ofNullable(giftBadge);
+
+ if (attachments != null && !attachments.isEmpty()) {
+ this.attachments = Optional.of(attachments);
+ } else {
+ this.attachments = Optional.empty();
+ }
+
+ if (sharedContacts != null && !sharedContacts.isEmpty()) {
+ this.contacts = Optional.of(sharedContacts);
+ } else {
+ this.contacts = Optional.empty();
+ }
+
+ if (previews != null && !previews.isEmpty()) {
+ this.previews = Optional.of(previews);
+ } else {
+ this.previews = Optional.empty();
+ }
+
+ if (mentions != null && !mentions.isEmpty()) {
+ this.mentions = Optional.of(mentions);
+ } else {
+ this.mentions = Optional.empty();
+ }
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * @return The message timestamp.
+ */
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ /**
+ * @return The message attachments (if any).
+ */
+ public Optional> getAttachments() {
+ return attachments;
+ }
+
+ /**
+ * @return The message body (if any).
+ */
+ public Optional getBody() {
+ return body;
+ }
+
+ /**
+ * @return The message group context (if any).
+ */
+ public Optional getGroupContext() {
+ return group;
+ }
+
+ public boolean isEndSession() {
+ return endSession;
+ }
+
+ public boolean isExpirationUpdate() {
+ return expirationUpdate;
+ }
+
+ public boolean isActivatePaymentsRequest() {
+ return getPayment().isPresent() &&
+ getPayment().get().getPaymentActivation().isPresent() &&
+ getPayment().get().getPaymentActivation().get().getType().equals(SignalServiceProtos.DataMessage.Payment.Activation.Type.REQUEST);
+ }
+
+ public boolean isPaymentsActivated() {
+ return getPayment().isPresent() &&
+ getPayment().get().getPaymentActivation().isPresent() &&
+ getPayment().get().getPaymentActivation().get().getType().equals(SignalServiceProtos.DataMessage.Payment.Activation.Type.ACTIVATED);
+ }
+
+ public boolean isProfileKeyUpdate() {
+ return profileKeyUpdate;
+ }
+
+ public boolean isGroupV2Message() {
+ return group.isPresent();
+ }
+
+ public boolean isGroupV2Update() {
+ return group.isPresent() &&
+ group.get().hasSignedGroupChange() &&
+ !hasRenderableContent();
+ }
+
+ public boolean isEmptyGroupV2Message() {
+ return isGroupV2Message() && !isGroupV2Update() && !hasRenderableContent();
+ }
+
+ /** Contains some user data that affects the conversation */
+ public boolean hasRenderableContent() {
+ return attachments.isPresent() ||
+ body.isPresent() ||
+ quote.isPresent() ||
+ contacts.isPresent() ||
+ previews.isPresent() ||
+ mentions.isPresent() ||
+ sticker.isPresent() ||
+ reaction.isPresent() ||
+ remoteDelete.isPresent();
+ }
+
+ public int getExpiresInSeconds() {
+ return expiresInSeconds;
+ }
+
+ public Optional getProfileKey() {
+ return profileKey;
+ }
+
+ public Optional getQuote() {
+ return quote;
+ }
+
+ public Optional> getSharedContacts() {
+ return contacts;
+ }
+
+ public Optional> getPreviews() {
+ return previews;
+ }
+
+ public Optional> getMentions() {
+ return mentions;
+ }
+
+ public Optional getSticker() {
+ return sticker;
+ }
+
+ public boolean isViewOnce() {
+ return viewOnce;
+ }
+
+ public Optional getReaction() {
+ return reaction;
+ }
+
+ public Optional getRemoteDelete() {
+ return remoteDelete;
+ }
+
+ public Optional getGroupCallUpdate() {
+ return groupCallUpdate;
+ }
+
+ public Optional getPayment() {
+ return payment;
+ }
+
+ public Optional getStoryContext() {
+ return storyContext;
+ }
+
+ public Optional getGiftBadge() {
+ return giftBadge;
+ }
+
+ public Optional getGroupId() {
+ byte[] groupId = null;
+
+ if (getGroupContext().isPresent() && getGroupContext().isPresent()) {
+ SignalServiceGroupV2 gv2 = getGroupContext().get();
+ groupId = GroupSecretParams.deriveFromMasterKey(gv2.getMasterKey())
+ .getPublicParams()
+ .getGroupIdentifier()
+ .serialize();
+ }
+
+ return Optional.ofNullable(groupId);
+ }
+
+ public static class Builder {
+
+ private List attachments = new LinkedList<>();
+ private List sharedContacts = new LinkedList<>();
+ private List previews = new LinkedList<>();
+ private List mentions = new LinkedList<>();
+
+ private long timestamp;
+ private SignalServiceGroupV2 groupV2;
+ private String body;
+ private boolean endSession;
+ private int expiresInSeconds;
+ private boolean expirationUpdate;
+ private byte[] profileKey;
+ private boolean profileKeyUpdate;
+ private Quote quote;
+ private Sticker sticker;
+ private boolean viewOnce;
+ private Reaction reaction;
+ private RemoteDelete remoteDelete;
+ private GroupCallUpdate groupCallUpdate;
+ private Payment payment;
+ private StoryContext storyContext;
+ private GiftBadge giftBadge;
+
+ private Builder() {}
+
+ public Builder withTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ return this;
+ }
+
+ public Builder asGroupMessage(SignalServiceGroupV2 group) {
+ this.groupV2 = group;
+ return this;
+ }
+
+ public Builder withAttachment(SignalServiceAttachment attachment) {
+ this.attachments.add(attachment);
+ return this;
+ }
+
+ public Builder withAttachments(List attachments) {
+ this.attachments.addAll(attachments);
+ return this;
+ }
+
+ public Builder withBody(String body) {
+ this.body = body;
+ return this;
+ }
+
+ public Builder asEndSessionMessage() {
+ return asEndSessionMessage(true);
+ }
+
+ public Builder asEndSessionMessage(boolean endSession) {
+ this.endSession = endSession;
+ return this;
+ }
+
+ public Builder asExpirationUpdate() {
+ return asExpirationUpdate(true);
+ }
+
+ public Builder asExpirationUpdate(boolean expirationUpdate) {
+ this.expirationUpdate = expirationUpdate;
+ return this;
+ }
+
+ public Builder withExpiration(int expiresInSeconds) {
+ this.expiresInSeconds = expiresInSeconds;
+ return this;
+ }
+
+ public Builder withProfileKey(byte[] profileKey) {
+ this.profileKey = profileKey;
+ return this;
+ }
+
+ public Builder asProfileKeyUpdate(boolean profileKeyUpdate) {
+ this.profileKeyUpdate = profileKeyUpdate;
+ return this;
+ }
+
+ public Builder withQuote(Quote quote) {
+ this.quote = quote;
+ return this;
+ }
+
+ public Builder withSharedContact(SharedContact contact) {
+ this.sharedContacts.add(contact);
+ return this;
+ }
+
+ public Builder withSharedContacts(List contacts) {
+ this.sharedContacts.addAll(contacts);
+ return this;
+ }
+
+ public Builder withPreviews(List previews) {
+ this.previews.addAll(previews);
+ return this;
+ }
+
+ public Builder withMentions(List mentions) {
+ this.mentions.addAll(mentions);
+ return this;
+ }
+
+ public Builder withSticker(Sticker sticker) {
+ this.sticker = sticker;
+ return this;
+ }
+
+ public Builder withViewOnce(boolean viewOnce) {
+ this.viewOnce = viewOnce;
+ return this;
+ }
+
+ public Builder withReaction(Reaction reaction) {
+ this.reaction = reaction;
+ return this;
+ }
+
+ public Builder withRemoteDelete(RemoteDelete remoteDelete) {
+ this.remoteDelete = remoteDelete;
+ return this;
+ }
+
+ public Builder withGroupCallUpdate(GroupCallUpdate groupCallUpdate) {
+ this.groupCallUpdate = groupCallUpdate;
+ return this;
+ }
+
+ public Builder withPayment(Payment payment) {
+ this.payment = payment;
+ return this;
+ }
+
+ public Builder withStoryContext(StoryContext storyContext) {
+ this.storyContext = storyContext;
+ return this;
+ }
+
+ public Builder withGiftBadge(GiftBadge giftBadge) {
+ this.giftBadge = giftBadge;
+ return this;
+ }
+
+ public SignalServiceDataMessage build() {
+ if (timestamp == 0) timestamp = System.currentTimeMillis();
+ return new SignalServiceDataMessage(timestamp, groupV2, attachments, body, endSession,
+ expiresInSeconds, expirationUpdate, profileKey,
+ profileKeyUpdate, quote, sharedContacts, previews,
+ mentions, sticker, viewOnce, reaction, remoteDelete,
+ groupCallUpdate,
+ payment,
+ storyContext,
+ giftBadge);
+ }
+ }
+
+ public static class Quote {
+ private final long id;
+ private final ServiceId author;
+ private final String text;
+ private final List attachments;
+ private final List mentions;
+ private final Type type;
+
+ public Quote(long id,
+ ServiceId author,
+ String text,
+ List attachments,
+ List mentions,
+ Type type)
+ {
+ this.id = id;
+ this.author = author;
+ this.text = text;
+ this.attachments = attachments;
+ this.mentions = mentions;
+ this.type = type;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public ServiceId getAuthor() {
+ return author;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public List getAttachments() {
+ return attachments;
+ }
+
+ public List getMentions() {
+ return mentions;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public enum Type {
+ NORMAL(SignalServiceProtos.DataMessage.Quote.Type.NORMAL),
+ GIFT_BADGE(SignalServiceProtos.DataMessage.Quote.Type.GIFT_BADGE);
+
+ private final SignalServiceProtos.DataMessage.Quote.Type protoType;
+
+ Type(SignalServiceProtos.DataMessage.Quote.Type protoType) {
+ this.protoType = protoType;
+ }
+
+ public SignalServiceProtos.DataMessage.Quote.Type getProtoType() {
+ return protoType;
+ }
+
+ public static Type fromProto(SignalServiceProtos.DataMessage.Quote.Type protoType) {
+ for (final Type value : values()) {
+ if (value.protoType == protoType) {
+ return value;
+ }
+ }
+
+ return NORMAL;
+ }
+ }
+
+ public static class QuotedAttachment {
+ private final String contentType;
+ private final String fileName;
+ private final SignalServiceAttachment thumbnail;
+
+ public QuotedAttachment(String contentType, String fileName, SignalServiceAttachment thumbnail) {
+ this.contentType = contentType;
+ this.fileName = fileName;
+ this.thumbnail = thumbnail;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public SignalServiceAttachment getThumbnail() {
+ return thumbnail;
+ }
+ }
+ }
+
+ public static class Sticker {
+ private final byte[] packId;
+ private final byte[] packKey;
+ private final int stickerId;
+ private final String emoji;
+ private final SignalServiceAttachment attachment;
+
+ public Sticker(byte[] packId, byte[] packKey, int stickerId, String emoji, SignalServiceAttachment attachment) {
+ this.packId = packId;
+ this.packKey = packKey;
+ this.stickerId = stickerId;
+ this.emoji = emoji;
+ this.attachment = attachment;
+ }
+
+ public byte[] getPackId() {
+ return packId;
+ }
+
+ public byte[] getPackKey() {
+ return packKey;
+ }
+
+ public int getStickerId() {
+ return stickerId;
+ }
+
+ public String getEmoji() {
+ return emoji;
+ }
+
+ public SignalServiceAttachment getAttachment() {
+ return attachment;
+ }
+ }
+
+ public static class Reaction {
+ private final String emoji;
+ private final boolean remove;
+ private final ServiceId targetAuthor;
+ private final long targetSentTimestamp;
+
+ public Reaction(String emoji, boolean remove, ServiceId targetAuthor, long targetSentTimestamp) {
+ this.emoji = emoji;
+ this.remove = remove;
+ this.targetAuthor = targetAuthor;
+ this.targetSentTimestamp = targetSentTimestamp;
+ }
+
+ public String getEmoji() {
+ return emoji;
+ }
+
+ public boolean isRemove() {
+ return remove;
+ }
+
+ public ServiceId getTargetAuthor() {
+ return targetAuthor;
+ }
+
+ public long getTargetSentTimestamp() {
+ return targetSentTimestamp;
+ }
+ }
+
+ public static class RemoteDelete {
+ private final long targetSentTimestamp;
+
+ public RemoteDelete(long targetSentTimestamp) {
+ this.targetSentTimestamp = targetSentTimestamp;
+ }
+
+ public long getTargetSentTimestamp() {
+ return targetSentTimestamp;
+ }
+ }
+
+ public static class Mention {
+ private final ServiceId serviceId;
+ private final int start;
+ private final int length;
+
+ public Mention(ServiceId serviceId, int start, int length) {
+ this.serviceId = serviceId;
+ this.start = start;
+ this.length = length;
+ }
+
+ public ServiceId getServiceId() {
+ return serviceId;
+ }
+
+ public int getStart() {
+ return start;
+ }
+
+ public int getLength() {
+ return length;
+ }
+ }
+
+ public static class GroupCallUpdate {
+ private final String eraId;
+
+ public GroupCallUpdate(String eraId) {
+ this.eraId = eraId;
+ }
+
+ public String getEraId() {
+ return eraId;
+ }
+ }
+
+ public static class PaymentNotification {
+
+ private final byte[] receipt;
+ private final String note;
+
+ public PaymentNotification(byte[] receipt, String note) {
+ this.receipt = receipt;
+ this.note = note;
+ }
+
+ public byte[] getReceipt() {
+ return receipt;
+ }
+
+ public String getNote() {
+ return note;
+ }
+ }
+
+ public static class PaymentActivation {
+ private final SignalServiceProtos.DataMessage.Payment.Activation.Type type;
+
+ public PaymentActivation(SignalServiceProtos.DataMessage.Payment.Activation.Type type) {
+ this.type = type;
+ }
+
+ public SignalServiceProtos.DataMessage.Payment.Activation.Type getType() {
+ return type;
+ }
+ }
+
+ public static class Payment {
+ private final Optional paymentNotification;
+ private final Optional paymentActivation;
+
+ public Payment(PaymentNotification paymentNotification, PaymentActivation paymentActivation) {
+ this.paymentNotification = Optional.ofNullable(paymentNotification);
+ this.paymentActivation = Optional.ofNullable(paymentActivation);
+ }
+
+ public Optional getPaymentNotification() {
+ return paymentNotification;
+ }
+
+ public Optional getPaymentActivation() {
+ return paymentActivation;
+ }
+ }
+
+ public static class StoryContext {
+ private final ServiceId authorServiceId;
+ private final long sentTimestamp;
+
+ public StoryContext(ServiceId authorServiceId, long sentTimestamp) {
+ this.authorServiceId = authorServiceId;
+ this.sentTimestamp = sentTimestamp;
+ }
+
+ public ServiceId getAuthorServiceId() {
+ return authorServiceId;
+ }
+
+ public long getSentTimestamp() {
+ return sentTimestamp;
+ }
+ }
+
+ public static class GiftBadge {
+ private final ReceiptCredentialPresentation receiptCredentialPresentation;
+
+ public GiftBadge(ReceiptCredentialPresentation receiptCredentialPresentation) {
+ this.receiptCredentialPresentation = receiptCredentialPresentation;
+ }
+
+ public ReceiptCredentialPresentation getReceiptCredentialPresentation() {
+ return receiptCredentialPresentation;
+ }
+ }
+}
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.kt
deleted file mode 100644
index 46b10ab935..0000000000
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.kt
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2014-2016 Open Whisper Systems
- *
- * Licensed according to the LICENSE file in this repository.
- */
-package org.whispersystems.signalservice.api.messages
-
-import org.signal.libsignal.zkgroup.groups.GroupSecretParams
-import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation
-import org.whispersystems.signalservice.api.messages.shared.SharedContact
-import org.whispersystems.signalservice.api.push.ServiceId
-import org.whispersystems.signalservice.api.util.OptionalUtil.asOptional
-import org.whispersystems.signalservice.api.util.OptionalUtil.emptyIfStringEmpty
-import java.util.LinkedList
-import java.util.Optional
-import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage.Payment as PaymentProto
-import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage.Quote as QuoteProto
-
-/**
- * Represents a decrypted Signal Service data message.
- *
- * @param timestamp The sent timestamp.
- * @param groupContext The group information (or null if none).
- * @param attachments The attachments (or null if none).
- * @param body The message contents.
- * @param isEndSession Flag indicating whether this message should close a session.
- * @param expiresInSeconds Number of seconds in which the message should disappear after being seen.
- */
-class SignalServiceDataMessage private constructor(
- val timestamp: Long,
- val groupContext: Optional,
- val attachments: Optional>,
- val body: Optional,
- val isEndSession: Boolean,
- val expiresInSeconds: Int,
- val isExpirationUpdate: Boolean,
- val profileKey: Optional,
- val isProfileKeyUpdate: Boolean,
- val quote: Optional,
- val sharedContacts: Optional>,
- val previews: Optional>,
- val mentions: Optional>,
- val sticker: Optional,
- val isViewOnce: Boolean,
- val reaction: Optional,
- val remoteDelete: Optional,
- val groupCallUpdate: Optional,
- val payment: Optional,
- val storyContext: Optional,
- val giftBadge: Optional
-) {
- val isActivatePaymentsRequest: Boolean = payment.map { it.isActivationRequest }.orElse(false)
- val isPaymentsActivated: Boolean = payment.map { it.isActivation }.orElse(false)
-
- val groupId: Optional = groupContext.map { GroupSecretParams.deriveFromMasterKey(it.masterKey).publicParams.groupIdentifier.serialize() }
- val isGroupV2Message: Boolean = groupContext.isPresent
-
- /** Contains some user data that affects the conversation */
- private val hasRenderableContent: Boolean =
- this.attachments.isPresent ||
- this.body.isPresent ||
- this.quote.isPresent ||
- this.sharedContacts.isPresent ||
- this.previews.isPresent ||
- this.mentions.isPresent ||
- this.sticker.isPresent ||
- this.reaction.isPresent ||
- this.remoteDelete.isPresent
-
- val isGroupV2Update: Boolean = groupContext.isPresent && groupContext.get().hasSignedGroupChange() && !hasRenderableContent
- val isEmptyGroupV2Message: Boolean = isGroupV2Message && !isGroupV2Update && !hasRenderableContent
-
- class Builder {
- private var timestamp: Long = 0
- private var groupV2: SignalServiceGroupV2? = null
- private val attachments: MutableList = LinkedList()
- private var body: String? = null
- private var endSession: Boolean = false
- private var expiresInSeconds: Int = 0
- private var expirationUpdate: Boolean = false
- private var profileKey: ByteArray? = null
- private var profileKeyUpdate: Boolean = false
- private var quote: Quote? = null
- private val sharedContacts: MutableList = LinkedList()
- private val previews: MutableList = LinkedList()
- private val mentions: MutableList = LinkedList()
- private var sticker: Sticker? = null
- private var viewOnce: Boolean = false
- private var reaction: Reaction? = null
- private var remoteDelete: RemoteDelete? = null
- private var groupCallUpdate: GroupCallUpdate? = null
- private var payment: Payment? = null
- private var storyContext: StoryContext? = null
- private var giftBadge: GiftBadge? = null
-
- fun withTimestamp(timestamp: Long): Builder {
- this.timestamp = timestamp
- return this
- }
-
- fun asGroupMessage(group: SignalServiceGroupV2?): Builder {
- groupV2 = group
- return this
- }
-
- fun withAttachment(attachment: SignalServiceAttachment?): Builder {
- attachment?.let { attachments.add(attachment) }
- return this
- }
-
- fun withAttachments(attachments: List?): Builder {
- attachments?.let { this.attachments.addAll(attachments) }
- return this
- }
-
- fun withBody(body: String?): Builder {
- this.body = body
- return this
- }
-
- @JvmOverloads
- fun asEndSessionMessage(endSession: Boolean = true): Builder {
- this.endSession = endSession
- return this
- }
-
- @JvmOverloads
- fun asExpirationUpdate(expirationUpdate: Boolean = true): Builder {
- this.expirationUpdate = expirationUpdate
- return this
- }
-
- fun withExpiration(expiresInSeconds: Int): Builder {
- this.expiresInSeconds = expiresInSeconds
- return this
- }
-
- fun withProfileKey(profileKey: ByteArray?): Builder {
- this.profileKey = profileKey
- return this
- }
-
- fun asProfileKeyUpdate(profileKeyUpdate: Boolean): Builder {
- this.profileKeyUpdate = profileKeyUpdate
- return this
- }
-
- fun withQuote(quote: Quote?): Builder {
- this.quote = quote
- return this
- }
-
- fun withSharedContact(contact: SharedContact?): Builder {
- contact?.let { sharedContacts.add(contact) }
- return this
- }
-
- fun withSharedContacts(contacts: List?): Builder {
- contacts?.let { sharedContacts.addAll(contacts) }
- return this
- }
-
- fun withPreviews(previews: List?): Builder {
- previews?.let { this.previews.addAll(previews) }
- return this
- }
-
- fun withMentions(mentions: List?): Builder {
- mentions?.let { this.mentions.addAll(mentions) }
- return this
- }
-
- fun withSticker(sticker: Sticker?): Builder {
- this.sticker = sticker
- return this
- }
-
- fun withViewOnce(viewOnce: Boolean): Builder {
- this.viewOnce = viewOnce
- return this
- }
-
- fun withReaction(reaction: Reaction?): Builder {
- this.reaction = reaction
- return this
- }
-
- fun withRemoteDelete(remoteDelete: RemoteDelete?): Builder {
- this.remoteDelete = remoteDelete
- return this
- }
-
- fun withGroupCallUpdate(groupCallUpdate: GroupCallUpdate?): Builder {
- this.groupCallUpdate = groupCallUpdate
- return this
- }
-
- fun withPayment(payment: Payment?): Builder {
- this.payment = payment
- return this
- }
-
- fun withStoryContext(storyContext: StoryContext?): Builder {
- this.storyContext = storyContext
- return this
- }
-
- fun withGiftBadge(giftBadge: GiftBadge?): Builder {
- this.giftBadge = giftBadge
- return this
- }
-
- fun build(): SignalServiceDataMessage {
- if (timestamp == 0L) {
- timestamp = System.currentTimeMillis()
- }
-
- return SignalServiceDataMessage(
- timestamp = timestamp,
- groupContext = groupV2.asOptional(),
- attachments = attachments.asOptional(),
- body = body.emptyIfStringEmpty(),
- isEndSession = endSession,
- expiresInSeconds = expiresInSeconds,
- isExpirationUpdate = expirationUpdate,
- profileKey = profileKey.asOptional(),
- isProfileKeyUpdate = profileKeyUpdate,
- quote = quote.asOptional(),
- sharedContacts = sharedContacts.asOptional(),
- previews = previews.asOptional(),
- mentions = mentions.asOptional(),
- sticker = sticker.asOptional(),
- isViewOnce = viewOnce,
- reaction = reaction.asOptional(),
- remoteDelete = remoteDelete.asOptional(),
- groupCallUpdate = groupCallUpdate.asOptional(),
- payment = payment.asOptional(),
- storyContext = storyContext.asOptional(),
- giftBadge = giftBadge.asOptional()
- )
- }
- }
-
- data class Quote(
- val id: Long,
- val author: ServiceId,
- val text: String,
- val attachments: List,
- val mentions: List,
- val type: Type
- ) {
- enum class Type(val protoType: QuoteProto.Type) {
- NORMAL(QuoteProto.Type.NORMAL),
- GIFT_BADGE(QuoteProto.Type.GIFT_BADGE);
-
- companion object {
- @JvmStatic
- fun fromProto(protoType: QuoteProto.Type): Type {
- return values().firstOrNull { it.protoType == protoType } ?: NORMAL
- }
- }
- }
-
- data class QuotedAttachment(val contentType: String, val fileName: String, val thumbnail: SignalServiceAttachment)
- }
- class Sticker(val packId: ByteArray, val packKey: ByteArray, val stickerId: Int, val emoji: String, val attachment: SignalServiceAttachment)
- data class Reaction(val emoji: String, val isRemove: Boolean, val targetAuthor: ServiceId, val targetSentTimestamp: Long)
- data class RemoteDelete(val targetSentTimestamp: Long)
- data class Mention(val serviceId: ServiceId, val start: Int, val length: Int)
- data class GroupCallUpdate(val eraId: String)
- class PaymentNotification(val receipt: ByteArray, val note: String)
- data class PaymentActivation(val type: PaymentProto.Activation.Type)
- class Payment(paymentNotification: PaymentNotification?, paymentActivation: PaymentActivation?) {
- val paymentNotification: Optional = Optional.ofNullable(paymentNotification)
- val paymentActivation: Optional = Optional.ofNullable(paymentActivation)
- val isActivationRequest: Boolean = paymentActivation != null && paymentActivation.type == PaymentProto.Activation.Type.REQUEST
- val isActivation: Boolean = paymentActivation != null && paymentActivation.type == PaymentProto.Activation.Type.ACTIVATED
- }
- data class StoryContext(val authorServiceId: ServiceId, val sentTimestamp: Long)
- data class GiftBadge(val receiptCredentialPresentation: ReceiptCredentialPresentation)
-
- companion object {
- @JvmStatic
- fun newBuilder(): Builder {
- return Builder()
- }
- }
-}
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.java
new file mode 100644
index 0000000000..52b56ae0e4
--- /dev/null
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.java
@@ -0,0 +1,53 @@
+package org.whispersystems.signalservice.api.util;
+
+import com.google.protobuf.ByteString;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+public final class OptionalUtil {
+
+ private OptionalUtil() { }
+
+ @SafeVarargs
+ public static Optional or(Optional... optionals) {
+ return Arrays.stream(optionals)
+ .filter(Optional::isPresent)
+ .findFirst()
+ .orElse(Optional.empty());
+ }
+
+ public static boolean byteArrayEquals(Optional a, Optional b) {
+ if (a.isPresent() != b.isPresent()) {
+ return false;
+ } else if (a.isPresent()) {
+ return Arrays.equals(a.get(), b.get());
+ } else {
+ return true;
+ }
+ }
+
+ public static int byteArrayHashCode(Optional bytes) {
+ if (bytes.isPresent()) {
+ return Arrays.hashCode(bytes.get());
+ } else {
+ return 0;
+ }
+ }
+
+ public static Optional absentIfEmpty(String value) {
+ if (value == null || value.length() == 0) {
+ return Optional.empty();
+ } else {
+ return Optional.of(value);
+ }
+ }
+
+ public static Optional absentIfEmpty(ByteString value) {
+ if (value == null || value.isEmpty()) {
+ return Optional.empty();
+ } else {
+ return Optional.of(value.toByteArray());
+ }
+ }
+}
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.kt
deleted file mode 100644
index 469d2419fd..0000000000
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.whispersystems.signalservice.api.util
-
-import com.google.protobuf.ByteString
-import java.util.Optional
-
-object OptionalUtil {
- @JvmStatic
- @SafeVarargs
- fun or(vararg optionals: Optional): Optional {
- return optionals.firstOrNull { it.isPresent } ?: Optional.empty()
- }
-
- @JvmStatic
- fun byteArrayEquals(a: Optional, b: Optional): Boolean {
- return if (a.isPresent != b.isPresent) {
- false
- } else if (a.isPresent) {
- a.get().contentEquals(b.get())
- } else {
- true
- }
- }
-
- @JvmStatic
- fun byteArrayHashCode(bytes: Optional): Int {
- return if (bytes.isPresent) {
- bytes.get().contentHashCode()
- } else {
- 0
- }
- }
-
- @JvmStatic
- fun absentIfEmpty(value: String?): Optional {
- return if (value == null || value.isEmpty()) {
- Optional.empty()
- } else {
- Optional.of(value)
- }
- }
-
- @JvmStatic
- fun absentIfEmpty(value: ByteString?): Optional {
- return if (value == null || value.isEmpty) {
- Optional.empty()
- } else {
- Optional.of(value.toByteArray())
- }
- }
-
- @JvmStatic
- fun emptyIfListEmpty(list: List?): Optional> {
- return list.asOptional()
- }
-
- fun E?.asOptional(): Optional {
- return Optional.ofNullable(this)
- }
-
- fun List?.asOptional(): Optional> {
- return Optional.ofNullable(this?.takeIf { it.isNotEmpty() })
- }
-
- fun String?.emptyIfStringEmpty(): Optional {
- return absentIfEmpty(this)
- }
-}