Add support for admin delete.

This commit is contained in:
Michelle Tang
2026-02-20 14:44:34 -05:00
committed by Cody Henthorne
parent 1968438ebb
commit 071fbfd916
45 changed files with 648 additions and 132 deletions

View File

@@ -188,7 +188,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
const val UNIDENTIFIED = "unidentified"
const val REACTIONS_UNREAD = "reactions_unread"
const val REACTIONS_LAST_SEEN = "reactions_last_seen"
const val REMOTE_DELETED = "remote_deleted"
const val SERVER_GUID = "server_guid"
const val RECEIPT_TIMESTAMP = "receipt_timestamp"
const val EXPORT_STATE = "export_state"
@@ -223,6 +222,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
const val PINNED_UNTIL = "pinned_until"
const val PINNING_MESSAGE_ID = "pinning_message_id"
const val PINNED_AT = "pinned_at"
const val DELETED_BY = "deleted_by"
const val QUOTE_NOT_PRESENT_ID = 0L
const val QUOTE_TARGET_MISSING_ID = -1L
@@ -273,7 +273,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
$VIEW_ONCE INTEGER DEFAULT 0,
$REACTIONS_UNREAD INTEGER DEFAULT 0,
$REACTIONS_LAST_SEEN INTEGER DEFAULT -1,
$REMOTE_DELETED INTEGER DEFAULT 0,
$MENTIONS_SELF INTEGER DEFAULT 0,
$NOTIFIED_TIMESTAMP INTEGER DEFAULT 0,
$SERVER_GUID TEXT DEFAULT NULL,
@@ -292,7 +291,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
$VOTES_LAST_SEEN INTEGER DEFAULT 0,
$PINNED_UNTIL INTEGER DEFAULT 0,
$PINNING_MESSAGE_ID INTEGER DEFAULT 0,
$PINNED_AT INTEGER DEFAULT 0
$PINNED_AT INTEGER DEFAULT 0,
$DELETED_BY INTEGER DEFAULT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE
)
"""
@@ -325,7 +325,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
"CREATE INDEX IF NOT EXISTS $INDEX_THREAD_UNREAD_COUNT ON $TABLE_NAME ($THREAD_ID) WHERE $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $ORIGINAL_MESSAGE_ID IS NULL AND $READ = 0",
"CREATE INDEX IF NOT EXISTS message_votes_unread_index ON $TABLE_NAME ($VOTES_UNREAD)",
"CREATE INDEX IF NOT EXISTS message_pinned_until_index ON $TABLE_NAME ($PINNED_UNTIL)",
"CREATE INDEX IF NOT EXISTS message_pinned_at_index ON $TABLE_NAME ($PINNED_AT)"
"CREATE INDEX IF NOT EXISTS message_pinned_at_index ON $TABLE_NAME ($PINNED_AT)",
"CREATE INDEX IF NOT EXISTS message_deleted_by_index ON $TABLE_NAME ($DELETED_BY)"
)
private val MMS_PROJECTION_BASE = arrayOf(
@@ -366,7 +367,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
VIEW_ONCE,
REACTIONS_UNREAD,
REACTIONS_LAST_SEEN,
REMOTE_DELETED,
MENTIONS_SELF,
NOTIFIED_TIMESTAMP,
VIEWED_COLUMN,
@@ -381,7 +381,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
MESSAGE_EXTRAS,
VOTES_UNREAD,
VOTES_LAST_SEEN,
PINNED_UNTIL
PINNED_UNTIL,
DELETED_BY
)
private val MMS_PROJECTION: Array<String> = MMS_PROJECTION_BASE + "NULL AS ${AttachmentTable.ATTACHMENT_JSON_ALIAS}"
@@ -427,7 +428,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
) AS ${AttachmentTable.ATTACHMENT_JSON_ALIAS}
""".toSingleLine()
private const val IS_STORY_CLAUSE = "$STORY_TYPE > 0 AND $REMOTE_DELETED = 0"
private const val IS_STORY_CLAUSE = "$STORY_TYPE > 0 AND $DELETED_BY IS NULL"
private const val RAW_ID_WHERE = "$TABLE_NAME.$ID = ?"
private val SNIPPET_QUERY =
@@ -1600,7 +1601,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
JOIN ${ThreadTable.TABLE_NAME} ON $TABLE_NAME.$THREAD_ID = ${ThreadTable.TABLE_NAME}.${ThreadTable.ID}
WHERE
$STORY_TYPE > 0 AND
$REMOTE_DELETED = 0
$DELETED_BY IS NULL
${if (isOutgoingOnly) " AND is_outgoing != 0" else ""}
ORDER BY
is_unread DESC,
@@ -2218,12 +2219,12 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
AppDependencies.databaseObserver.notifyConversationListListeners()
}
fun markAsRemoteDelete(targetMessage: MessageRecord) {
fun markAsRemoteDelete(targetMessage: MessageRecord, deletedBy: RecipientId) {
writableDatabase.withinTransaction { db ->
val hasRevision = (targetMessage as? MmsMessageRecord)?.latestRevisionId?.id != null
if (hasRevision || targetMessage.isEditMessage) {
val latestRevisionId = (targetMessage as? MmsMessageRecord)?.latestRevisionId?.id ?: targetMessage.id
markAsRemoteDeleteInternal(latestRevisionId)
markAsRemoteDeleteInternal(latestRevisionId, deletedBy)
getPreviousEditIds(latestRevisionId).map { id ->
db.update(TABLE_NAME)
.values(
@@ -2235,22 +2236,22 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
deleteMessage(id)
}
} else {
markAsRemoteDeleteInternal(targetMessage.id)
markAsRemoteDeleteInternal(targetMessage.id, deletedBy)
}
}
}
fun markAsRemoteDelete(messageId: Long) {
fun markAsDeleteBySelf(messageId: Long) {
val targetMessage: MessageRecord = getMessageRecord(messageId)
markAsRemoteDelete(targetMessage)
markAsRemoteDelete(targetMessage, Recipient.self().id)
}
private fun markAsRemoteDeleteInternal(messageId: Long) {
private fun markAsRemoteDeleteInternal(messageId: Long, deletedBy: RecipientId) {
var deletedAttachments = false
writableDatabase.withinTransaction { db ->
db.update(TABLE_NAME)
.values(
REMOTE_DELETED to 1,
DELETED_BY to deletedBy.toLong(),
BODY to null,
QUOTE_BODY to null,
QUOTE_AUTHOR to null,
@@ -4012,7 +4013,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
fun deleteRemotelyDeletedStory(messageId: Long) {
if (readableDatabase.exists(TABLE_NAME).where("$ID = ? AND $REMOTE_DELETED = ?", messageId, 1).run()) {
if (readableDatabase.exists(TABLE_NAME).where("$ID = ? AND $DELETED_BY > 0", messageId).run()) {
deleteMessage(messageId)
} else {
Log.i(TAG, "Unable to delete remotely deleted story: $messageId")
@@ -4595,7 +4596,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val targetMessageDateReceived: Long = readableDatabase
.select(DATE_RECEIVED, LATEST_REVISION_ID)
.from(TABLE_NAME)
.where("$DATE_SENT = $quoteId AND $FROM_RECIPIENT_ID = ? AND $REMOTE_DELETED = 0 AND $SCHEDULED_DATE = -1", authorId)
.where("$DATE_SENT = $quoteId AND $FROM_RECIPIENT_ID = ? AND $DELETED_BY IS NULL AND $SCHEDULED_DATE = -1", authorId)
.run()
.readToSingleObject { cursor ->
val latestRevisionId = cursor.requireLongOrNull(LATEST_REVISION_ID)
@@ -4626,7 +4627,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
fun getMessagePositionInConversation(threadId: Long, receivedTimestamp: Long, authorId: RecipientId): Int {
val validMessageExists: Boolean = readableDatabase
.exists(TABLE_NAME)
.where("$DATE_RECEIVED = $receivedTimestamp AND $FROM_RECIPIENT_ID = ? AND $REMOTE_DELETED = 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL", authorId)
.where("$DATE_RECEIVED = $receivedTimestamp AND $FROM_RECIPIENT_ID = ? AND $DELETED_BY IS NULL AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL", authorId)
.run()
if (!validMessageExists) {
@@ -5455,7 +5456,13 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
.where("$QUOTE_AUTHOR = ?", fromId)
.run()
Log.d(TAG, "Remapped $fromId to $toId. fromRecipient: $fromCount, toRecipient: $toCount, quoteAuthor: $quoteAuthorCount")
val deletedByCount = writableDatabase
.update(TABLE_NAME)
.values(DELETED_BY to toId.serialize())
.where("$DELETED_BY = ?", fromId)
.run()
Log.d(TAG, "Remapped $fromId to $toId. fromRecipient: $fromCount, toRecipient: $toCount, quoteAuthor: $quoteAuthorCount, deletedBy: $deletedByCount")
}
override fun remapThread(fromId: Long, toId: Long) {
@@ -6255,7 +6262,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val expireTimerVersion = cursor.requireInt(EXPIRE_TIMER_VERSION)
val unidentified = cursor.requireBoolean(UNIDENTIFIED)
val isViewOnce = cursor.requireBoolean(VIEW_ONCE)
val remoteDelete = cursor.requireBoolean(REMOTE_DELETED)
val mentionsSelf = cursor.requireBoolean(MENTIONS_SELF)
val notifiedTimestamp = cursor.requireLong(NOTIFIED_TIMESTAMP)
var isViewed = cursor.requireBoolean(VIEWED_COLUMN)
@@ -6269,6 +6275,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val editCount = cursor.requireInt(REVISION_NUMBER)
val isRead = cursor.requireBoolean(READ)
val pinnedUntil = cursor.requireLong(PINNED_UNTIL)
val deletedBy = cursor.requireLongOrNull(DELETED_BY)?.let { RecipientId.from(it) }
val messageExtraBytes = cursor.requireBlob(MESSAGE_EXTRAS)
val messageExtras = messageExtraBytes?.let { MessageExtras.ADAPTER.decode(it) }
@@ -6346,7 +6353,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
previews,
unidentified,
emptyList(),
remoteDelete,
mentionsSelf,
notifiedTimestamp,
isViewed,
@@ -6364,6 +6370,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
editCount,
isRead,
pinnedUntil,
deletedBy,
messageExtras
)
}

View File

@@ -147,7 +147,7 @@ class StorySendTable(context: Context, databaseHelper: SignalDatabase) : Databas
AND $MESSAGE_ID IN (
SELECT ${MessageTable.ID}
FROM ${MessageTable.TABLE_NAME}
WHERE ${MessageTable.REMOTE_DELETED} = 0
WHERE ${MessageTable.DELETED_BY} IS NULL
)
)
"""
@@ -232,7 +232,7 @@ class StorySendTable(context: Context, databaseHelper: SignalDatabase) : Databas
.where(
"""
$SENT_TIMESTAMP = ? AND
(SELECT ${MessageTable.REMOTE_DELETED} FROM ${MessageTable.TABLE_NAME} WHERE ${MessageTable.ID} = $MESSAGE_ID) = 0
(SELECT ${MessageTable.DELETED_BY} FROM ${MessageTable.TABLE_NAME} WHERE ${MessageTable.ID} = $MESSAGE_ID) IS NULL
""",
sentTimestamp
)
@@ -330,7 +330,7 @@ class StorySendTable(context: Context, databaseHelper: SignalDatabase) : Databas
val messagesWithoutAnyReceivers = localRows.map { it.messageId }.distinct() - remoteRows.map { it.messageId }.distinct()
messagesWithoutAnyReceivers.forEach {
SignalDatabase.messages.markAsRemoteDelete(it)
SignalDatabase.messages.markAsDeleteBySelf(it)
SignalDatabase.messages.deleteRemotelyDeletedStory(it)
}
}
@@ -344,7 +344,7 @@ class StorySendTable(context: Context, databaseHelper: SignalDatabase) : Databas
$TABLE_NAME.$RECIPIENT_ID,
$ALLOWS_REPLIES,
$DISTRIBUTION_ID,
${MessageTable.REMOTE_DELETED}
${MessageTable.DELETED_BY}
FROM $TABLE_NAME
INNER JOIN ${MessageTable.TABLE_NAME} ON ${MessageTable.TABLE_NAME}.${MessageTable.ID} = $TABLE_NAME.$MESSAGE_ID
WHERE $TABLE_NAME.$SENT_TIMESTAMP = ?
@@ -353,7 +353,7 @@ class StorySendTable(context: Context, databaseHelper: SignalDatabase) : Databas
).use { cursor ->
val results: MutableMap<RecipientId, SentStorySyncManifest.Entry> = mutableMapOf()
while (cursor.moveToNext()) {
val isRemoteDeleted = CursorUtil.requireBoolean(cursor, MessageTable.REMOTE_DELETED)
val isRemoteDeleted = !CursorUtil.isNull(cursor, MessageTable.DELETED_BY)
val recipientId = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID))
val distributionId = DistributionId.from(CursorUtil.requireString(cursor, DISTRIBUTION_ID))
val distributionIdList: List<DistributionId> = if (isRemoteDeleted) emptyList() else listOf(distributionId)

View File

@@ -2094,7 +2094,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
val extras: Extra? = if (record.isScheduled()) {
Extra.forScheduledMessage(authorId)
} else if (record.isRemoteDelete) {
Extra.forRemoteDelete(authorId)
Extra.forRemoteDelete(authorId, record.deletedBy!!)
} else if (record.isViewOnce) {
Extra.forViewOnce(authorId)
} else if (record.isMms && (record as MmsMessageRecord).slideDeck.stickerSlide != null) {
@@ -2280,7 +2280,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
isSticker = jsonObject.getBoolean("isSticker"),
stickerEmoji = jsonObject.getString("stickerEmoji"),
isAlbum = jsonObject.getBoolean("isAlbum"),
isRemoteDelete = jsonObject.getBoolean("isRemoteDelete"),
deletedBy = jsonObject.getString("deletedBy"),
isMessageRequestAccepted = jsonObject.getBoolean("isMessageRequestAccepted"),
isGv2Invite = jsonObject.getBoolean("isGv2Invite"),
groupAddedBy = jsonObject.getString("groupAddedBy"),
@@ -2353,8 +2353,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
@param:JsonProperty("isAlbum")
val isAlbum: Boolean = false,
@field:JsonProperty
@param:JsonProperty("isRemoteDelete")
val isRemoteDelete: Boolean = false,
@param:JsonProperty("deletedBy")
val deletedBy: String? = null,
@field:JsonProperty
@param:JsonProperty("isMessageRequestAccepted")
val isMessageRequestAccepted: Boolean = true,
@@ -2398,8 +2398,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
return Extra(isAlbum = true, individualRecipientId = individualRecipient.serialize())
}
fun forRemoteDelete(individualRecipient: RecipientId): Extra {
return Extra(isRemoteDelete = true, individualRecipientId = individualRecipient.serialize())
fun forRemoteDelete(individualRecipient: RecipientId, deletedBy: RecipientId): Extra {
return Extra(deletedBy = deletedBy.serialize(), individualRecipientId = individualRecipient.serialize())
}
fun forMessageRequest(individualRecipient: RecipientId, isHidden: Boolean = false): Extra {

View File

@@ -155,6 +155,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V298_DoNotBackupRel
import org.thoughtcrime.securesms.database.helpers.migration.V299_AddAttachmentMetadataTable
import org.thoughtcrime.securesms.database.helpers.migration.V300_AddKeyTransparencyColumn
import org.thoughtcrime.securesms.database.helpers.migration.V301_RemoveCallLinkEpoch
import org.thoughtcrime.securesms.database.helpers.migration.V302_AddDeletedByColumn
import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSqliteDatabase
/**
@@ -316,10 +317,11 @@ object SignalDatabaseMigrations {
298 to V298_DoNotBackupReleaseNotes,
299 to V299_AddAttachmentMetadataTable,
300 to V300_AddKeyTransparencyColumn,
301 to V301_RemoveCallLinkEpoch
301 to V301_RemoveCallLinkEpoch,
302 to V302_AddDeletedByColumn
)
const val DATABASE_VERSION = 301
const val DATABASE_VERSION = 302
@JvmStatic
fun migrate(context: Application, db: SignalSqliteDatabase, oldVersion: Int, newVersion: Int) {

View File

@@ -0,0 +1,33 @@
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import org.signal.core.util.Stopwatch
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.database.SQLiteDatabase
/**
* Adds column to messages to track who has deleted a given message
*/
@Suppress("ClassName")
object V302_AddDeletedByColumn : SignalDatabaseMigration {
private val TAG = Log.tag(V302_AddDeletedByColumn::class.java)
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
val stopwatch = Stopwatch("migration", decimalPlaces = 2)
db.execSQL("ALTER TABLE message ADD COLUMN deleted_by INTEGER DEFAULT NULL REFERENCES recipient (_id) ON DELETE CASCADE")
stopwatch.split("add-column")
db.execSQL("UPDATE message SET deleted_by = from_recipient_id WHERE remote_deleted > 0")
stopwatch.split("copy-data")
db.execSQL("ALTER TABLE message DROP COLUMN remote_deleted")
stopwatch.split("drop-column")
db.execSQL("CREATE INDEX message_deleted_by_index ON message (deleted_by)")
stopwatch.split("create-index")
stopwatch.stop(TAG)
}
}

View File

@@ -53,13 +53,13 @@ public class InMemoryMessageRecord extends MessageRecord {
false,
false,
Collections.emptyList(),
false,
0,
false,
-1,
null,
0,
0,
null,
null);
}

View File

@@ -108,12 +108,12 @@ public abstract class MessageRecord extends DisplayRecord {
private final boolean unidentified;
private final List<ReactionRecord> reactions;
private final long serverTimestamp;
private final boolean remoteDelete;
private final long notifiedTimestamp;
private final long receiptTimestamp;
private final MessageId originalMessageId;
private final int revisionNumber;
private final long pinnedUntil;
private final RecipientId deletedBy;
private final MessageExtras messageExtras;
protected Boolean isJumboji = null;
@@ -130,13 +130,13 @@ public abstract class MessageRecord extends DisplayRecord {
boolean hasReadReceipt,
boolean unidentified,
@NonNull List<ReactionRecord> reactions,
boolean remoteDelete,
long notifiedTimestamp,
boolean viewed,
long receiptTimestamp,
@Nullable MessageId originalMessageId,
int revisionNumber,
long pinnedUntil,
@Nullable RecipientId deletedBy,
@Nullable MessageExtras messageExtras)
{
super(body, fromRecipient, toRecipient, dateSent, dateReceived,
@@ -153,12 +153,12 @@ public abstract class MessageRecord extends DisplayRecord {
this.unidentified = unidentified;
this.reactions = reactions;
this.serverTimestamp = dateServer;
this.remoteDelete = remoteDelete;
this.notifiedTimestamp = notifiedTimestamp;
this.receiptTimestamp = receiptTimestamp;
this.originalMessageId = originalMessageId;
this.revisionNumber = revisionNumber;
this.pinnedUntil = pinnedUntil;
this.deletedBy = deletedBy;
this.messageExtras = messageExtras;
}
@@ -785,6 +785,10 @@ public abstract class MessageRecord extends DisplayRecord {
return pinnedUntil;
}
public @Nullable RecipientId getDeletedBy() {
return deletedBy;
}
public boolean isInMemoryMessageRecord() {
return false;
}
@@ -832,7 +836,7 @@ public abstract class MessageRecord extends DisplayRecord {
}
public boolean isRemoteDelete() {
return remoteDelete;
return deletedBy != null;
}
public @NonNull List<ReactionRecord> getReactions() {

View File

@@ -103,7 +103,6 @@ public class MmsMessageRecord extends MessageRecord {
@NonNull List<LinkPreview> linkPreviews,
boolean unidentified,
@NonNull List<ReactionRecord> reactions,
boolean remoteDelete,
boolean mentionsSelf,
long notifiedTimestamp,
boolean viewed,
@@ -121,12 +120,13 @@ public class MmsMessageRecord extends MessageRecord {
int revisionNumber,
boolean isRead,
long pinnedUntil,
@Nullable RecipientId deletedBy,
@Nullable MessageExtras messageExtras)
{
super(id, body, fromRecipient, fromDeviceId, toRecipient,
dateSent, dateReceived, dateServer, threadId, Status.STATUS_NONE, hasDeliveryReceipt,
mailbox, mismatches, failures, subscriptionId, expiresIn, expireStarted, expireTimerVersion, hasReadReceipt,
unidentified, reactions, remoteDelete, notifiedTimestamp, viewed, receiptTimestamp, originalMessageId, revisionNumber, pinnedUntil, messageExtras);
unidentified, reactions, notifiedTimestamp, viewed, receiptTimestamp, originalMessageId, revisionNumber, pinnedUntil, deletedBy, messageExtras);
this.slideDeck = slideDeck;
this.quote = quote;
@@ -337,17 +337,17 @@ public class MmsMessageRecord extends MessageRecord {
public @NonNull MmsMessageRecord withReactions(@NonNull List<ReactionRecord> reactions) {
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, isRemoteDelete(), mentionsSelf,
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getPoll(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getMessageExtras());
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getMessageExtras());
}
public @NonNull MmsMessageRecord withoutQuote() {
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
hasReadReceipt(), null, getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
hasReadReceipt(), null, getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getPoll(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getMessageExtras());
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getMessageExtras());
}
public @NonNull MmsMessageRecord withAttachments(@NonNull List<DatabaseAttachment> attachments) {
@@ -367,34 +367,34 @@ public class MmsMessageRecord extends MessageRecord {
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), slideDeck,
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
hasReadReceipt(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
hasReadReceipt(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getPoll(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getMessageExtras());
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getMessageExtras());
}
public @NonNull MmsMessageRecord withPayment(@NonNull Payment payment) {
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment, getCall(), getPoll(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getMessageExtras());
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getMessageExtras());
}
public @NonNull MmsMessageRecord withCall(@Nullable CallTable.Call call) {
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), call, getPoll(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getMessageExtras());
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getMessageExtras());
}
public @NonNull MmsMessageRecord withPoll(@Nullable PollRecord poll) {
return new MmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), hasDeliveryReceipt(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), poll, getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getMessageExtras());
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getMessageExtras());
}
private static @NonNull List<Contact> updateContacts(@NonNull List<Contact> contacts, @NonNull Map<AttachmentId, DatabaseAttachment> attachmentIdMap) {

View File

@@ -209,6 +209,14 @@ public final class ThreadRecord {
}
}
public @NonNull RecipientId getDeletedByRecipientId() {
if (extra != null && extra.getDeletedBy() != null) {
return RecipientId.from(extra.getDeletedBy());
} else {
return RecipientId.UNKNOWN;
}
}
public @NonNull RecipientId getGroupMessageSender() {
RecipientId threadRecipientId = getRecipient().getId();
RecipientId individualRecipientId = getIndividualRecipientId();