Signal-Android/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.kt

289 wiersze
10 KiB
Kotlin

/*
* 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<SignalServiceGroupV2>,
val attachments: Optional<List<SignalServiceAttachment>>,
val body: Optional<String>,
val isEndSession: Boolean,
val expiresInSeconds: Int,
val isExpirationUpdate: Boolean,
val profileKey: Optional<ByteArray>,
val isProfileKeyUpdate: Boolean,
val quote: Optional<Quote>,
val sharedContacts: Optional<List<SharedContact>>,
val previews: Optional<List<SignalServicePreview>>,
val mentions: Optional<List<Mention>>,
val sticker: Optional<Sticker>,
val isViewOnce: Boolean,
val reaction: Optional<Reaction>,
val remoteDelete: Optional<RemoteDelete>,
val groupCallUpdate: Optional<GroupCallUpdate>,
val payment: Optional<Payment>,
val storyContext: Optional<StoryContext>,
val giftBadge: Optional<GiftBadge>
) {
val isActivatePaymentsRequest: Boolean = payment.map { it.isActivationRequest }.orElse(false)
val isPaymentsActivated: Boolean = payment.map { it.isActivation }.orElse(false)
val groupId: Optional<ByteArray> = 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<SignalServiceAttachment> = LinkedList<SignalServiceAttachment>()
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<SharedContact> = LinkedList<SharedContact>()
private val previews: MutableList<SignalServicePreview> = LinkedList<SignalServicePreview>()
private val mentions: MutableList<Mention> = LinkedList<Mention>()
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<SignalServiceAttachment>?): 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<SharedContact>?): Builder {
contacts?.let { sharedContacts.addAll(contacts) }
return this
}
fun withPreviews(previews: List<SignalServicePreview>?): Builder {
previews?.let { this.previews.addAll(previews) }
return this
}
fun withMentions(mentions: List<Mention>?): 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<QuotedAttachment>?,
val mentions: List<Mention>?,
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<PaymentNotification> = Optional.ofNullable(paymentNotification)
val paymentActivation: Optional<PaymentActivation> = 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()
}
}
}