diff --git a/build.gradle b/build.gradle
index 946d118de..cd53100e3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -79,6 +79,7 @@ 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 b6a80016e..fc25766bb 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -2292,6 +2292,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -2300,6 +2308,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -2308,6 +2324,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -2316,6 +2340,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -2324,6 +2356,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -2332,6 +2372,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -2340,6 +2388,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -2348,6 +2404,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -2356,6 +2420,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -2364,6 +2436,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -2372,6 +2452,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -3424,6 +3512,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
@@ -3459,6 +3552,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
@@ -3599,6 +3697,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
@@ -3619,6 +3722,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
@@ -3699,6 +3807,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
@@ -3749,6 +3862,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
@@ -3784,6 +3902,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
@@ -3824,6 +3947,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
diff --git a/libsignal/service/build.gradle b/libsignal/service/build.gradle
index 2216625f7..5d99b0ef2 100644
--- a/libsignal/service/build.gradle
+++ b/libsignal/service/build.gradle
@@ -1,9 +1,11 @@
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"
@@ -41,6 +43,8 @@ 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 6cdda8678..c59f66e2c 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,27 +721,29 @@ public final class SignalServiceContent {
metadata.getSenderDevice());
}
- 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);
+ 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();
}
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
deleted file mode 100644
index 3c7b0273f..000000000
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java
+++ /dev/null
@@ -1,729 +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.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
new file mode 100644
index 000000000..46b10ab93
--- /dev/null
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.kt
@@ -0,0 +1,288 @@
+/*
+ * 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
deleted file mode 100644
index 52b56ae0e..000000000
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.java
+++ /dev/null
@@ -1,53 +0,0 @@
-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
new file mode 100644
index 000000000..469d2419f
--- /dev/null
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.kt
@@ -0,0 +1,67 @@
+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)
+ }
+}