mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 00:59:49 +01:00
Add Delete for Me sync support.
This commit is contained in:
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.messages
|
||||
import ProtoUtil.isNotEmpty
|
||||
import com.squareup.wire.Message
|
||||
import okio.ByteString
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.libsignal.protocol.message.DecryptionErrorMessage
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
@@ -25,8 +26,10 @@ import org.whispersystems.signalservice.internal.push.DataMessage
|
||||
import org.whispersystems.signalservice.internal.push.DataMessage.Payment
|
||||
import org.whispersystems.signalservice.internal.push.GroupContextV2
|
||||
import org.whispersystems.signalservice.internal.push.StoryMessage
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage.Sent
|
||||
import org.whispersystems.signalservice.internal.push.TypingMessage
|
||||
import org.whispersystems.signalservice.internal.util.Util
|
||||
import java.util.Optional
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
@@ -186,6 +189,11 @@ object SignalServiceProtoUtil {
|
||||
return Money.picoMobileCoin(this)
|
||||
}
|
||||
|
||||
fun SyncMessage.Builder.pad(length: Int = 512): SyncMessage.Builder {
|
||||
padding(Util.getRandomLengthSecretBytes(length).toByteString())
|
||||
return this
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified MessageType : Message<MessageType, BuilderType>, BuilderType : Message.Builder<MessageType, BuilderType>> Message.Builder<MessageType, BuilderType>.buildWith(block: BuilderType.() -> Unit): MessageType {
|
||||
block(this as BuilderType)
|
||||
|
||||
@@ -158,6 +158,7 @@ object SyncMessageProcessor {
|
||||
syncMessage.callEvent != null -> handleSynchronizeCallEvent(syncMessage.callEvent!!, envelope.timestamp!!)
|
||||
syncMessage.callLinkUpdate != null -> handleSynchronizeCallLink(syncMessage.callLinkUpdate!!, envelope.timestamp!!)
|
||||
syncMessage.callLogEvent != null -> handleSynchronizeCallLogEvent(syncMessage.callLogEvent!!, envelope.timestamp!!)
|
||||
syncMessage.deleteForMe != null -> handleSynchronizeDeleteForMe(context, syncMessage.deleteForMe!!, envelope.timestamp!!, earlyMessageCacheEntry)
|
||||
else -> warn(envelope.timestamp!!, "Contains no known sync types...")
|
||||
}
|
||||
}
|
||||
@@ -1451,4 +1452,146 @@ object SyncMessageProcessor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSynchronizeDeleteForMe(context: Context, deleteForMe: SyncMessage.DeleteForMe, envelopeTimestamp: Long, earlyMessageCacheEntry: EarlyMessageCacheEntry?) {
|
||||
if (!FeatureFlags.deleteSyncEnabled()) {
|
||||
warn(envelopeTimestamp, "Delete for me sync message dropped as support not enabled")
|
||||
return
|
||||
}
|
||||
|
||||
log(envelopeTimestamp, "Synchronize delete message messageDeletes=${deleteForMe.messageDeletes.size} conversationDeletes=${deleteForMe.conversationDeletes.size} localOnlyConversationDeletes=${deleteForMe.localOnlyConversationDeletes.size}")
|
||||
|
||||
if (deleteForMe.messageDeletes.isNotEmpty()) {
|
||||
handleSynchronizeMessageDeletes(deleteForMe.messageDeletes, envelopeTimestamp, earlyMessageCacheEntry)
|
||||
}
|
||||
|
||||
if (deleteForMe.conversationDeletes.isNotEmpty()) {
|
||||
handleSynchronizeConversationDeletes(deleteForMe.conversationDeletes, envelopeTimestamp)
|
||||
}
|
||||
|
||||
if (deleteForMe.localOnlyConversationDeletes.isNotEmpty()) {
|
||||
handleSynchronizeLocalOnlyConversationDeletes(deleteForMe.localOnlyConversationDeletes, envelopeTimestamp)
|
||||
}
|
||||
|
||||
ApplicationDependencies.getMessageNotifier().updateNotification(context)
|
||||
}
|
||||
|
||||
private fun handleSynchronizeMessageDeletes(messageDeletes: List<SyncMessage.DeleteForMe.MessageDeletes>, envelopeTimestamp: Long, earlyMessageCacheEntry: EarlyMessageCacheEntry?) {
|
||||
val messagesToDelete: List<MessageTable.SyncMessageId> = messageDeletes
|
||||
.asSequence()
|
||||
.map { it.messages }
|
||||
.flatten()
|
||||
.mapNotNull { it.toSyncMessageId(envelopeTimestamp) }
|
||||
.toList()
|
||||
|
||||
val unhandled: List<MessageTable.SyncMessageId> = SignalDatabase.messages.deleteMessages(messagesToDelete)
|
||||
|
||||
for (syncMessage in unhandled) {
|
||||
warn(envelopeTimestamp, "[handleSynchronizeDeleteForMe] Could not find matching message! timestamp: ${syncMessage.timetamp} author: ${syncMessage.recipientId}")
|
||||
if (earlyMessageCacheEntry != null) {
|
||||
ApplicationDependencies.getEarlyMessageCache().store(syncMessage.recipientId, syncMessage.timetamp, earlyMessageCacheEntry)
|
||||
}
|
||||
}
|
||||
|
||||
if (unhandled.isNotEmpty() && earlyMessageCacheEntry != null) {
|
||||
PushProcessEarlyMessagesJob.enqueue()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSynchronizeConversationDeletes(conversationDeletes: List<SyncMessage.DeleteForMe.ConversationDelete>, envelopeTimestamp: Long) {
|
||||
for (delete in conversationDeletes) {
|
||||
val threadRecipientId: RecipientId? = delete.conversation?.toRecipientId()
|
||||
|
||||
if (threadRecipientId == null) {
|
||||
warn(envelopeTimestamp, "[handleSynchronizeDeleteForMe] Could not find matching conversation recipient")
|
||||
continue
|
||||
}
|
||||
|
||||
val threadId = SignalDatabase.threads.getThreadIdFor(threadRecipientId)
|
||||
if (threadId == null) {
|
||||
log(envelopeTimestamp, "[handleSynchronizeDeleteForMe] No thread for matching conversation for recipient: $threadRecipientId")
|
||||
continue
|
||||
}
|
||||
|
||||
val mostRecentMessagesToDelete: List<MessageTable.SyncMessageId> = delete.mostRecentMessages.mapNotNull { it.toSyncMessageId(envelopeTimestamp) }
|
||||
val latestReceivedAt = SignalDatabase.messages.getLatestReceivedAt(threadId, mostRecentMessagesToDelete)
|
||||
|
||||
if (latestReceivedAt != null) {
|
||||
SignalDatabase.threads.trimThread(threadId = threadId, syncThreadTrimDeletes = false, trimBeforeDate = latestReceivedAt, inclusive = true)
|
||||
|
||||
if (delete.isFullDelete == true) {
|
||||
val deleted = SignalDatabase.threads.deleteConversationIfContainsOnlyLocal(threadId)
|
||||
|
||||
if (deleted) {
|
||||
log(envelopeTimestamp, "[handleSynchronizeDeleteForMe] Deleted thread with only local remaining")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn(envelopeTimestamp, "[handleSynchronizeDeleteForMe] Unable to find most recent received at timestamp for recipient: $threadRecipientId thread: $threadId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSynchronizeLocalOnlyConversationDeletes(conversationDeletes: List<SyncMessage.DeleteForMe.LocalOnlyConversationDelete>, envelopeTimestamp: Long) {
|
||||
for (delete in conversationDeletes) {
|
||||
val threadRecipientId: RecipientId? = delete.conversation?.toRecipientId()
|
||||
|
||||
if (threadRecipientId == null) {
|
||||
warn(envelopeTimestamp, "[handleSynchronizeDeleteForMe] Could not find matching conversation recipient")
|
||||
continue
|
||||
}
|
||||
|
||||
val threadId = SignalDatabase.threads.getThreadIdFor(threadRecipientId)
|
||||
if (threadId == null) {
|
||||
log(envelopeTimestamp, "[handleSynchronizeDeleteForMe] No thread for matching conversation for recipient: $threadRecipientId")
|
||||
continue
|
||||
}
|
||||
|
||||
val deleted = SignalDatabase.threads.deleteConversationIfContainsOnlyLocal(threadId)
|
||||
if (!deleted) {
|
||||
log(envelopeTimestamp, "[handleSynchronizeDeleteForMe] Thread is not local only or already empty recipient: $threadRecipientId thread: $threadId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun SyncMessage.DeleteForMe.ConversationIdentifier.toRecipientId(): RecipientId? {
|
||||
return when {
|
||||
threadGroupId != null -> {
|
||||
try {
|
||||
val groupId: GroupId = GroupId.push(threadGroupId!!)
|
||||
Recipient.externalPossiblyMigratedGroup(groupId).id
|
||||
} catch (e: BadGroupIdException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
threadAci != null -> {
|
||||
ServiceId.parseOrNull(threadAci)?.let {
|
||||
SignalDatabase.recipients.getOrInsertFromServiceId(it)
|
||||
}
|
||||
}
|
||||
|
||||
threadE164 != null -> {
|
||||
SignalDatabase.recipients.getOrInsertFromE164(threadE164!!)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun SyncMessage.DeleteForMe.AddressableMessage.toSyncMessageId(envelopeTimestamp: Long): MessageTable.SyncMessageId? {
|
||||
return if (this.sentTimestamp != null && (this.authorAci != null || this.authorE164 != null)) {
|
||||
val serviceId = ServiceId.parseOrNull(this.authorAci)
|
||||
val id = if (serviceId != null) {
|
||||
SignalDatabase.recipients.getOrInsertFromServiceId(serviceId)
|
||||
} else {
|
||||
SignalDatabase.recipients.getOrInsertFromE164(this.authorE164!!)
|
||||
}
|
||||
|
||||
MessageTable.SyncMessageId(id, this.sentTimestamp!!)
|
||||
} else {
|
||||
warn(envelopeTimestamp, "[handleSynchronizeDeleteForMe] Invalid delete sync missing timestamp or author")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user