Add message editing feature.

This commit is contained in:
Clark
2023-04-14 16:29:26 -04:00
committed by Cody Henthorne
parent 4f06a0d27c
commit 07f6baf7c1
73 changed files with 2051 additions and 304 deletions

View File

@@ -37,6 +37,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import org.json.JSONArray;
import org.json.JSONException;
import org.signal.core.util.CursorUtil;
import org.signal.core.util.SQLiteDatabaseExtensionsKt;
import org.signal.core.util.SetUtil;
import org.signal.core.util.SqlUtil;
import org.signal.core.util.StreamUtil;
@@ -1484,6 +1485,16 @@ public class AttachmentTable extends DatabaseTable {
return EncryptedMediaDataSource.createFor(attachmentSecret, dataInfo.file, dataInfo.random, dataInfo.length);
}
public void duplicateAttachmentsForMessage(long destinationMessageId, long sourceMessageId) {
SQLiteDatabaseExtensionsKt.withinTransaction(getWritableDatabase(), db -> {
db.execSQL("CREATE TEMPORARY TABLE tmp_part AS SELECT * FROM " + TABLE_NAME + " WHERE " + MMS_ID + " = ?", SqlUtil.buildArgs(sourceMessageId));
db.execSQL("UPDATE tmp_part SET " + ROW_ID + " = NULL, " + MMS_ID + " = ?", SqlUtil.buildArgs(destinationMessageId));
db.execSQL("INSERT INTO " + TABLE_NAME + " SELECT * FROM tmp_part");
db.execSQL("DROP TABLE tmp_part");
return 0;
});
}
@VisibleForTesting
static class DataInfo {
private final File file;

View File

@@ -141,6 +141,7 @@ class DraftTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
const val QUOTE = "quote"
const val BODY_RANGES = "mention"
const val VOICE_NOTE = "voice_note"
const val MESSAGE_EDIT = "message_edit"
}
}
@@ -159,6 +160,10 @@ class DraftTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
return firstOrNull { it.type == type }
}
fun shouldUpdateSnippet(): Boolean {
return none { it.type == Draft.MESSAGE_EDIT }
}
fun getSnippet(context: Context): String {
val textDraft = getDraftOfType(Draft.TEXT)
return if (textDraft != null) {

View File

@@ -134,6 +134,7 @@ import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.JsonUtils
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.MessageConstraintsUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.isStory
@@ -203,6 +204,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
const val STORY_TYPE = "story_type"
const val PARENT_STORY_ID = "parent_story_id"
const val SCHEDULED_DATE = "scheduled_date"
const val LATEST_REVISION_ID = "latest_revision_id"
const val ORIGINAL_MESSAGE_ID = "original_message_id"
const val REVISION_NUMBER = "revision_number"
const val CREATE_TABLE = """
CREATE TABLE $TABLE_NAME (
@@ -254,12 +258,15 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
$PARENT_STORY_ID INTEGER DEFAULT 0,
$EXPORT_STATE BLOB DEFAULT NULL,
$EXPORTED INTEGER DEFAULT 0,
$SCHEDULED_DATE INTEGER DEFAULT -1
$SCHEDULED_DATE INTEGER DEFAULT -1,
$LATEST_REVISION_ID INTEGER DEFAULT NULL REFERENCES $TABLE_NAME ($ID) ON DELETE CASCADE,
$ORIGINAL_MESSAGE_ID INTEGER DEFAULT NULL REFERENCES $TABLE_NAME ($ID) ON DELETE CASCADE,
$REVISION_NUMBER INTEGER DEFAULT 0
)
"""
private const val INDEX_THREAD_DATE = "message_thread_date_index"
private const val INDEX_THREAD_STORY_SCHEDULED_DATE = "message_thread_story_parent_story_scheduled_date_index"
private const val INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID = "message_thread_story_parent_story_scheduled_date_latest_revision_id_index"
@JvmField
val CREATE_INDEXS = arrayOf(
@@ -271,8 +278,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
"CREATE INDEX IF NOT EXISTS message_reactions_unread_index ON $TABLE_NAME ($REACTIONS_UNREAD);",
"CREATE INDEX IF NOT EXISTS message_story_type_index ON $TABLE_NAME ($STORY_TYPE);",
"CREATE INDEX IF NOT EXISTS message_parent_story_id_index ON $TABLE_NAME ($PARENT_STORY_ID);",
"CREATE INDEX IF NOT EXISTS $INDEX_THREAD_STORY_SCHEDULED_DATE ON $TABLE_NAME ($THREAD_ID, $DATE_RECEIVED, $STORY_TYPE, $PARENT_STORY_ID, $SCHEDULED_DATE);",
"CREATE INDEX IF NOT EXISTS message_quote_id_quote_author_scheduled_date_index ON $TABLE_NAME ($QUOTE_ID, $QUOTE_AUTHOR, $SCHEDULED_DATE);",
"CREATE INDEX IF NOT EXISTS $INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID ON $TABLE_NAME ($THREAD_ID, $DATE_RECEIVED, $STORY_TYPE, $PARENT_STORY_ID, $SCHEDULED_DATE, $LATEST_REVISION_ID);",
"CREATE INDEX IF NOT EXISTS message_quote_id_quote_author_scheduled_date_latest_revision_id_index ON $TABLE_NAME ($QUOTE_ID, $QUOTE_AUTHOR, $SCHEDULED_DATE, $LATEST_REVISION_ID);",
"CREATE INDEX IF NOT EXISTS message_exported_index ON $TABLE_NAME ($EXPORTED);",
"CREATE INDEX IF NOT EXISTS message_id_type_payment_transactions_index ON $TABLE_NAME ($ID,$TYPE) WHERE $TYPE & ${MessageTypes.SPECIAL_TYPE_PAYMENTS_NOTIFICATION} != 0;"
)
@@ -323,7 +330,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
MESSAGE_RANGES,
STORY_TYPE,
PARENT_STORY_ID,
SCHEDULED_DATE
SCHEDULED_DATE,
LATEST_REVISION_ID,
ORIGINAL_MESSAGE_ID,
REVISION_NUMBER
)
private val MMS_PROJECTION: Array<String> = MMS_PROJECTION_BASE + "NULL AS ${AttachmentTable.ATTACHMENT_JSON_ALIAS}"
@@ -380,7 +390,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
$TYPE & ${MessageTypes.GROUP_V2_LEAVE_BITS} != ${MessageTypes.GROUP_V2_LEAVE_BITS} AND
$STORY_TYPE = 0 AND
$PARENT_STORY_ID <= 0 AND
$SCHEDULED_DATE = -1 AND
$SCHEDULED_DATE = -1 AND
$LATEST_REVISION_ID IS NULL AND
$TYPE NOT IN (
${MessageTypes.PROFILE_CHANGE_TYPE},
${MessageTypes.GV1_MIGRATION_TYPE},
@@ -965,8 +976,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
@JvmOverloads
fun insertMessageInbox(message: IncomingTextMessage, type: Long = MessageTypes.BASE_INBOX_TYPE): Optional<InsertResult> {
var type = type
fun insertMessageInbox(message: IncomingTextMessage, editedMessage: MediaMmsMessageRecord? = null): Optional<InsertResult> {
var type = MessageTypes.BASE_INBOX_TYPE
var tryToCollapseJoinRequestEvents = false
if (message.isJoined) {
@@ -1059,13 +1070,19 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
values.put(THREAD_ID, threadId)
values.put(SERVER_GUID, message.serverGuid)
if (editedMessage != null) {
values.put(ORIGINAL_MESSAGE_ID, editedMessage.getOriginalOrOwnMessageId().id)
} else {
values.putNull(ORIGINAL_MESSAGE_ID)
}
return if (message.isPush && isDuplicate(message, threadId)) {
Log.w(TAG, "Duplicate message (" + message.sentTimestampMillis + "), ignoring...")
Optional.empty()
} else {
val messageId = writableDatabase.insert(TABLE_NAME, null, values)
if (unread) {
if (unread && editedMessage == null) {
threads.incrementUnread(threadId, 1, 0)
}
@@ -1084,6 +1101,48 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
}
fun insertEditMessageInbox(threadId: Long, mediaMessage: IncomingMediaMessage, targetMessage: MediaMmsMessageRecord): Optional<InsertResult> {
val insertResult = insertSecureDecryptedMessageInbox(mediaMessage, threadId, targetMessage)
if (insertResult.isPresent) {
val (messageId) = insertResult.get()
if (targetMessage.expireStarted > 0) {
markExpireStarted(messageId, targetMessage.expireStarted)
}
writableDatabase.update(TABLE_NAME)
.values(LATEST_REVISION_ID to messageId)
.where("$ID = ? OR $LATEST_REVISION_ID = ?", targetMessage.id, targetMessage.id)
.run()
reactions.moveReactionsToNewMessage(newMessageId = messageId, previousId = targetMessage.id)
}
return insertResult
}
fun insertEditMessageInbox(textMessage: IncomingTextMessage, targetMessage: MediaMmsMessageRecord): Optional<InsertResult> {
val insertResult = insertMessageInbox(textMessage, targetMessage)
if (insertResult.isPresent) {
val (messageId) = insertResult.get()
if (targetMessage.expireStarted > 0) {
markExpireStarted(messageId, targetMessage.expireStarted)
}
writableDatabase.update(TABLE_NAME)
.values(LATEST_REVISION_ID to messageId)
.where("$ID_WHERE OR $LATEST_REVISION_ID = ?", targetMessage.id, targetMessage.id)
.run()
reactions.moveReactionsToNewMessage(newMessageId = messageId, previousId = targetMessage.id)
}
return insertResult
}
fun insertProfileNameChangeMessages(recipient: Recipient, newProfileName: String, previousProfileName: String) {
writableDatabase.withinTransaction { db ->
val groupRecords = groups.getGroupsContainingMember(recipient.id, false)
@@ -1676,7 +1735,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
fun getScheduledMessageCountForThread(threadId: Long): Int {
return readableDatabase
.select("COUNT(*)")
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE")
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID")
.where("$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE != ?", threadId, 0, 0, -1)
.run()
.readToSingleInt()
@@ -1685,8 +1744,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
fun getMessageCountForThread(threadId: Long): Int {
return readableDatabase
.select("COUNT(*)")
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE")
.where("$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ?", threadId, 0, 0, -1)
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID")
.where("$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ? AND $LATEST_REVISION_ID IS NULL", threadId, 0, 0, -1)
.run()
.readToSingleInt()
}
@@ -1694,8 +1753,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
fun getMessageCountForThread(threadId: Long, beforeTime: Long): Int {
return readableDatabase
.select("COUNT(*)")
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE")
.where("$THREAD_ID = ? AND $DATE_RECEIVED < ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ?", threadId, beforeTime, 0, 0, -1)
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID")
.where("$THREAD_ID = ? AND $DATE_RECEIVED < ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ? AND $LATEST_REVISION_ID IS NULL", threadId, beforeTime, 0, 0, -1)
.run()
.readToSingleInt()
}
@@ -1746,6 +1805,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val query = """
$THREAD_ID = ? AND
$STORY_TYPE = 0 AND
$LATEST_REVISION_ID IS NULL AND
$PARENT_STORY_ID <= 0 AND
(
NOT $TYPE & ${MessageTypes.IGNORABLE_TYPESMASK_WHEN_COUNTING} AND
@@ -1844,11 +1904,31 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
}
private fun getOriginalEditedMessageRecord(messageId: Long): Long {
return readableDatabase.select(ID)
.from(TABLE_NAME)
.where("$TABLE_NAME.$LATEST_REVISION_ID = ?", messageId)
.orderBy("$ID DESC")
.limit(1)
.run()
.readToSingleLong(0)
}
fun getMessages(messageIds: Collection<Long?>): MmsReader {
val ids = TextUtils.join(",", messageIds)
return mmsReaderFor(rawQueryWithAttachments("$TABLE_NAME.$ID IN ($ids)", null))
}
fun getMessageEditHistory(id: Long): MmsReader {
val cursor = readableDatabase.select(*MMS_PROJECTION)
.from(TABLE_NAME)
.where("$TABLE_NAME.$ID = ? OR $TABLE_NAME.$LATEST_REVISION_ID = ?", id, id)
.orderBy("$TABLE_NAME.$ID DESC")
.run()
return mmsReaderFor(cursor)
}
private fun updateMailboxBitmask(id: Long, maskOff: Long, maskOn: Long, threadId: Optional<Long>) {
writableDatabase.withinTransaction { db ->
db.execSQL(
@@ -2378,6 +2458,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
null
}
val editedMessage = getOriginalEditedMessageRecord(messageId)
OutgoingMessage(
recipient = threadRecipient,
body = body,
@@ -2399,7 +2481,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
giftBadge = giftBadge,
isSecure = MessageTypes.isSecureType(outboxType),
bodyRanges = messageRanges,
scheduledDate = scheduledDate
scheduledDate = scheduledDate,
messageToEdit = editedMessage
)
}
} ?: throw NoSuchMessageException("No record found for id: $messageId")
@@ -2410,7 +2493,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
retrieved: IncomingMediaMessage,
contentLocation: String,
candidateThreadId: Long,
mailbox: Long
mailbox: Long,
editedMessage: MediaMmsMessageRecord?
): Optional<InsertResult> {
val threadId = if (candidateThreadId == -1L || retrieved.isGroupMessage) {
getThreadIdFor(retrieved)
@@ -2443,7 +2527,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
PARENT_STORY_ID to if (retrieved.parentStoryId != null) retrieved.parentStoryId.serialize() else 0,
READ to if (silentUpdate || retrieved.isExpirationUpdate) 1 else 0,
UNIDENTIFIED to retrieved.isUnidentified,
SERVER_GUID to retrieved.serverGuid
SERVER_GUID to retrieved.serverGuid,
LATEST_REVISION_ID to null,
ORIGINAL_MESSAGE_ID to editedMessage?.getOriginalOrOwnMessageId()?.id,
REVISION_NUMBER to (editedMessage?.revisionNumber?.inc() ?: 0)
)
val quoteAttachments: MutableList<Attachment> = mutableListOf()
@@ -2483,6 +2570,24 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
unarchive = true
)
if (editedMessage != null) {
if (retrieved.quote != null && editedMessage.quote != null) {
writableDatabase.execSQL(
"""
WITH o as (SELECT $QUOTE_ID, $QUOTE_AUTHOR, $QUOTE_BODY, $QUOTE_TYPE, $QUOTE_MISSING, $QUOTE_BODY_RANGES FROM $TABLE_NAME WHERE $ID = ${editedMessage.id})
UPDATE $TABLE_NAME
SET $QUOTE_ID = old.$QUOTE_ID, $QUOTE_AUTHOR = old.$QUOTE_AUTHOR, $QUOTE_BODY = old.$QUOTE_BODY, $QUOTE_TYPE = old.$QUOTE_TYPE, $QUOTE_MISSING = old.$QUOTE_MISSING, $QUOTE_BODY_RANGES = old.$QUOTE_BODY_RANGES
FROM o old
WHERE $TABLE_NAME.$ID = $messageId
"""
)
}
}
if (retrieved.attachments.isEmpty() && editedMessage?.id != null && attachments.getAttachmentsForMessage(editedMessage.id).isNotEmpty()) {
attachments.duplicateAttachmentsForMessage(messageId, editedMessage.id)
}
val isNotStoryGroupReply = retrieved.parentStoryId == null || !retrieved.parentStoryId.isGroupReply()
if (!MessageTypes.isPaymentsActivated(mailbox) && !MessageTypes.isPaymentsRequestToActivate(mailbox) && !MessageTypes.isExpirationTimerUpdate(mailbox) && !retrieved.storyType.isStory && isNotStoryGroupReply) {
@@ -2528,11 +2633,12 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
type = type or MessageTypes.SPECIAL_TYPE_PAYMENTS_ACTIVATED
}
return insertMessageInbox(retrieved, contentLocation, threadId, type)
return insertMessageInbox(retrieved, contentLocation, threadId, type, editedMessage = null)
}
@JvmOverloads
@Throws(MmsException::class)
fun insertSecureDecryptedMessageInbox(retrieved: IncomingMediaMessage, threadId: Long): Optional<InsertResult> {
fun insertSecureDecryptedMessageInbox(retrieved: IncomingMediaMessage, threadId: Long, edittedMediaMessage: MediaMmsMessageRecord? = null): Optional<InsertResult> {
var type = MessageTypes.BASE_INBOX_TYPE or MessageTypes.SECURE_MESSAGE_BIT
var hasSpecialType = false
@@ -2581,7 +2687,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
hasSpecialType = true
}
return insertMessageInbox(retrieved, "", threadId, type)
return insertMessageInbox(retrieved, "", threadId, type, edittedMediaMessage)
}
fun insertMessageInbox(notification: NotificationInd, subscriptionId: Int): Pair<Long, Long> {
@@ -2836,15 +2942,27 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
Log.w(TAG, "Found early delivery receipts for " + message.sentTimeMillis + ". Applying them.")
}
var editedMessage: MessageRecord? = null
if (message.isMessageEdit) {
try {
editedMessage = getMessageRecord(message.messageToEdit)
if (!MessageConstraintsUtil.isValidEditMessageSend(editedMessage)) {
throw MmsException("Message is not valid to edit")
}
} catch (e: NoSuchMessageException) {
throw MmsException("Unable to locate edited message", e)
}
}
val contentValues = ContentValues()
contentValues.put(DATE_SENT, message.sentTimeMillis)
contentValues.put(MMS_MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ)
contentValues.put(TYPE, type)
contentValues.put(THREAD_ID, threadId)
contentValues.put(READ, 1)
contentValues.put(DATE_RECEIVED, System.currentTimeMillis())
contentValues.put(DATE_RECEIVED, editedMessage?.dateReceived ?: System.currentTimeMillis())
contentValues.put(SMS_SUBSCRIPTION_ID, message.subscriptionId)
contentValues.put(EXPIRES_IN, message.expiresIn)
contentValues.put(EXPIRES_IN, editedMessage?.expiresIn ?: message.expiresIn)
contentValues.put(VIEW_ONCE, message.isViewOnce)
contentValues.put(FROM_RECIPIENT_ID, Recipient.self().id.serialize())
contentValues.put(FROM_DEVICE_ID, SignalStore.account().deviceId)
@@ -2854,6 +2972,14 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
contentValues.put(STORY_TYPE, message.storyType.code)
contentValues.put(PARENT_STORY_ID, if (message.parentStoryId != null) message.parentStoryId.serialize() else 0)
contentValues.put(SCHEDULED_DATE, message.scheduledDate)
contentValues.putNull(LATEST_REVISION_ID)
if (editedMessage != null) {
contentValues.put(ORIGINAL_MESSAGE_ID, editedMessage.getOriginalOrOwnMessageId().id)
contentValues.put(REVISION_NUMBER, editedMessage.revisionNumber + 1)
} else {
contentValues.putNull(ORIGINAL_MESSAGE_ID)
}
if (message.threadRecipient.isSelf && hasAudioAttachment(message.attachments)) {
contentValues.put(VIEWED_RECEIPT_COUNT, 1L)
@@ -2935,10 +3061,19 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
}
if (message.messageToEdit > 0) {
writableDatabase.update(TABLE_NAME)
.values(LATEST_REVISION_ID to messageId)
.where("$ID_WHERE OR $LATEST_REVISION_ID = ?", message.messageToEdit, message.messageToEdit)
.run()
reactions.moveReactionsToNewMessage(messageId, message.messageToEdit)
}
threads.updateLastSeenAndMarkSentAndLastScrolledSilenty(threadId)
if (!message.storyType.isStory) {
if (message.outgoingQuote == null) {
if (message.outgoingQuote == null && editedMessage == null) {
ApplicationDependencies.getDatabaseObserver().notifyMessageInsertObservers(threadId, MessageId(messageId))
} else {
ApplicationDependencies.getDatabaseObserver().notifyConversationListeners(threadId)
@@ -3273,6 +3408,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
reader.filterNotNull()
}
}
fun getAllRateLimitedMessageIds(): Set<Long> {
val db = databaseHelper.signalReadableDatabase
val where = "(" + TYPE + " & " + MessageTypes.TOTAL_MASK + " & " + MessageTypes.MESSAGE_RATE_LIMITED_BIT + ") > 0"
@@ -3389,7 +3525,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
private fun getMessagesInThreadAfterInclusive(threadId: Long, timestamp: Long, limit: Long): List<MessageRecord> {
val where = "$TABLE_NAME.$THREAD_ID = ? AND $TABLE_NAME.$DATE_RECEIVED >= ? AND $TABLE_NAME.$SCHEDULED_DATE = -1"
val where = "$TABLE_NAME.$THREAD_ID = ? AND $TABLE_NAME.$DATE_RECEIVED >= ? AND $TABLE_NAME.$SCHEDULED_DATE = -1 AND $TABLE_NAME.$LATEST_REVISION_ID IS NULL"
val args = buildArgs(threadId, timestamp)
return mmsReaderFor(rawQueryWithAttachments(where, args, false, limit)).use { reader ->
@@ -3858,7 +3994,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
fun getAllMessagesThatQuote(id: MessageId): List<MessageRecord> {
val targetMessage: MessageRecord = getMessageRecord(id.id)
val query = "$QUOTE_ID = ${targetMessage.dateSent} AND $QUOTE_AUTHOR = ${targetMessage.fromRecipient.id.serialize()} AND $SCHEDULED_DATE = -1"
val query = "$QUOTE_ID = ${targetMessage.dateSent} AND $QUOTE_AUTHOR = ${targetMessage.fromRecipient.id.serialize()} AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL"
val order = "$DATE_RECEIVED DESC"
val records: MutableList<MessageRecord> = ArrayList()
@@ -3873,8 +4009,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
fun getQuotedMessagePosition(threadId: Long, quoteId: Long, authorId: RecipientId): Int {
var position = 0
readableDatabase
.select(DATE_SENT, FROM_RECIPIENT_ID, REMOTE_DELETED)
.select(DATE_SENT, FROM_RECIPIENT_ID, REMOTE_DELETED, LATEST_REVISION_ID)
.from(TABLE_NAME)
.where("$THREAD_ID = $threadId AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1")
.orderBy("$DATE_RECEIVED DESC")
@@ -3887,8 +4024,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
return if (cursor.requireBoolean(REMOTE_DELETED)) {
-1
} else {
cursor.position
position
}
} else if (cursor.requireLong(LATEST_REVISION_ID) == 0L) {
position++
}
}
@@ -3899,7 +4038,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
readableDatabase
.select(DATE_RECEIVED, FROM_RECIPIENT_ID, REMOTE_DELETED)
.from(TABLE_NAME)
.where("$THREAD_ID = $threadId AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1")
.where("$THREAD_ID = $threadId AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL")
.orderBy("$DATE_RECEIVED DESC")
.run()
.forEach { cursor ->
@@ -3938,10 +4077,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
if (groupStoryId > 0) {
order = "$DATE_RECEIVED ASC"
selection = "$THREAD_ID = $threadId AND $DATE_RECEIVED < $receivedTimestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID = $groupStoryId AND $SCHEDULED_DATE = -1"
selection = "$THREAD_ID = $threadId AND $DATE_RECEIVED < $receivedTimestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID = $groupStoryId AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL"
} else {
order = "$DATE_RECEIVED DESC"
selection = "$THREAD_ID = $threadId AND $DATE_RECEIVED > $receivedTimestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1"
selection = "$THREAD_ID = $threadId AND $DATE_RECEIVED > $receivedTimestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL"
}
return readableDatabase
@@ -3957,7 +4096,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
return readableDatabase
.select(DATE_RECEIVED)
.from(TABLE_NAME)
.where("$DATE_RECEIVED > $date AND $SCHEDULED_DATE = -1")
.where("$DATE_RECEIVED > $date AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL")
.orderBy("$DATE_RECEIVED ASC")
.limit(1)
.run()
@@ -3968,7 +4107,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
return readableDatabase
.select("COUNT(*)")
.from(TABLE_NAME)
.where("$DATE_RECEIVED < $date AND $SCHEDULED_DATE = -1")
.where("$DATE_RECEIVED < $date AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL")
.run()
.readToSingleInt()
}
@@ -3986,7 +4125,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
return readableDatabase
.select("COUNT(*)")
.from(TABLE_NAME)
.where("$THREAD_ID = $threadId AND $DATE_RECEIVED >= $timestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1")
.where("$THREAD_ID = $threadId AND $DATE_RECEIVED >= $timestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL")
.run()
.readToSingleInt()
}
@@ -4017,7 +4156,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
return readableDatabase
.select("COUNT(*)")
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_DATE")
.where("$READ = 0 AND $STORY_TYPE = 0 AND $THREAD_ID = $threadId AND $PARENT_STORY_ID <= 0")
.where("$READ = 0 AND $STORY_TYPE = 0 AND $THREAD_ID = $threadId AND $PARENT_STORY_ID <= 0 AND $LATEST_REVISION_ID IS NULL")
.run()
.readToSingleInt()
}
@@ -4410,7 +4549,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
return readableDatabase
.select(*MMS_PROJECTION)
.from(TABLE_NAME)
.where("$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ?", threadId, 0, 0, -1)
.where("$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ? AND $LATEST_REVISION_ID IS NULL", threadId, 0, 0, -1)
.orderBy("$DATE_RECEIVED DESC")
.limit(limitStr)
.run()
@@ -4422,7 +4561,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
fun getScheduledMessagesInThread(threadId: Long): List<MessageRecord> {
val cursor = readableDatabase
.select(*MMS_PROJECTION)
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE")
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID")
.where("$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE != ?", threadId, 0, 0, -1)
.orderBy("$SCHEDULED_DATE DESC, $ID DESC")
.run()
@@ -4690,6 +4829,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
}
private fun MessageRecord.getOriginalOrOwnMessageId(): MessageId {
return this.originalMessageId ?: MessageId(this.id)
}
protected enum class ReceiptType(val columnName: String, val groupStatus: Int) {
READ(READ_RECEIPT_COUNT, GroupReceiptTable.STATUS_READ),
DELIVERY(DELIVERY_RECEIPT_COUNT, GroupReceiptTable.STATUS_DELIVERED),
@@ -4974,6 +5117,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val storyType = StoryType.fromCode(cursor.requireInt(STORY_TYPE))
val parentStoryId = ParentStoryId.deserialize(cursor.requireLong(PARENT_STORY_ID))
val scheduledDate = cursor.requireLong(SCHEDULED_DATE)
val latestRevisionId: MessageId? = cursor.requireLong(LATEST_REVISION_ID).let { if (it == 0L) null else MessageId(it) }
val originalMessageId: MessageId? = cursor.requireLong(ORIGINAL_MESSAGE_ID).let { if (it == 0L) null else MessageId(it) }
val editCount = cursor.requireInt(REVISION_NUMBER)
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
readReceiptCount = 0
@@ -5057,7 +5203,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
giftBadge,
null,
null,
scheduledDate
scheduledDate,
latestRevisionId,
originalMessageId,
editCount
)
}

View File

@@ -6,6 +6,7 @@ import android.database.Cursor
import org.signal.core.util.CursorUtil
import org.signal.core.util.SqlUtil
import org.signal.core.util.delete
import org.signal.core.util.update
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
@@ -177,4 +178,12 @@ class ReactionTable(context: Context, databaseHelper: SignalDatabase) : Database
.where("$MESSAGE_ID NOT IN (SELECT ${MessageTable.ID} FROM ${MessageTable.TABLE_NAME})")
.run()
}
fun moveReactionsToNewMessage(newMessageId: Long, previousId: Long) {
writableDatabase
.update(TABLE_NAME)
.values(MESSAGE_ID to newMessageId)
.where("$MESSAGE_ID = ?", previousId)
.run()
}
}

View File

@@ -77,7 +77,9 @@ class SearchTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
WHERE
$FTS_TABLE_NAME MATCH ? AND
${MessageTable.TABLE_NAME}.${MessageTable.TYPE} & ${MessageTypes.GROUP_V2_BIT} = 0 AND
${MessageTable.TABLE_NAME}.${MessageTable.TYPE} & ${MessageTypes.SPECIAL_TYPE_PAYMENTS_NOTIFICATION} = 0
${MessageTable.TABLE_NAME}.${MessageTable.TYPE} & ${MessageTypes.SPECIAL_TYPE_PAYMENTS_NOTIFICATION} = 0 AND
${MessageTable.TABLE_NAME}.${MessageTable.SCHEDULED_DATE} < 0 AND
${MessageTable.TABLE_NAME}.${MessageTable.LATEST_REVISION_ID} IS NULL
ORDER BY ${MessageTable.DATE_RECEIVED} DESC
LIMIT 500
"""
@@ -99,7 +101,11 @@ class SearchTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
INNER JOIN ${ThreadTable.TABLE_NAME} ON $FTS_TABLE_NAME.$THREAD_ID = ${ThreadTable.TABLE_NAME}.${ThreadTable.ID}
WHERE
$FTS_TABLE_NAME MATCH ? AND
${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID} = ?
${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID} = ? AND
${MessageTable.TABLE_NAME}.${MessageTable.TYPE} & ${MessageTypes.GROUP_V2_BIT} = 0 AND
${MessageTable.TABLE_NAME}.${MessageTable.TYPE} & ${MessageTypes.SPECIAL_TYPE_PAYMENTS_NOTIFICATION} = 0 AND
${MessageTable.TABLE_NAME}.${MessageTable.SCHEDULED_DATE} < 0 AND
${MessageTable.TABLE_NAME}.${MessageTable.LATEST_REVISION_ID} IS NULL
ORDER BY ${MessageTable.DATE_RECEIVED} DESC
LIMIT 500
"""

View File

@@ -161,7 +161,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
Log.i(TAG, "Upgrading database: $oldVersion, $newVersion")
val startTime = System.currentTimeMillis()
db.setForeignKeyConstraintsEnabled(false)
db.beginTransaction()
try {
migrate(context, db, oldVersion, newVersion)
@@ -169,7 +168,6 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
db.setTransactionSuccessful()
} finally {
db.endTransaction()
db.setForeignKeyConstraintsEnabled(true)
// We have to re-begin the transaction for the calling code (see comment at start of method)
db.beginTransaction()

View File

@@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V182_CallTableMigra
import org.thoughtcrime.securesms.database.helpers.migration.V183_CallLinkTableMigration
import org.thoughtcrime.securesms.database.helpers.migration.V184_CallLinkReplaceIndexMigration
import org.thoughtcrime.securesms.database.helpers.migration.V185_MessageRecipientsMigration
import org.thoughtcrime.securesms.database.helpers.migration.V186_AddEditMessageColumnsMigration
/**
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
@@ -49,7 +50,7 @@ object SignalDatabaseMigrations {
val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass)
const val DATABASE_VERSION = 185
const val DATABASE_VERSION = 186
@JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
@@ -200,6 +201,10 @@ object SignalDatabaseMigrations {
if (oldVersion < 185) {
V185_MessageRecipientsMigration.migrate(context, db, oldVersion, newVersion)
}
if (oldVersion < 186) {
V186_AddEditMessageColumnsMigration.migrate(context, db, oldVersion, newVersion)
}
}
@JvmStatic

View File

@@ -0,0 +1,189 @@
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import net.zetetic.database.sqlcipher.SQLiteDatabase
import org.signal.core.util.Stopwatch
import org.signal.core.util.logging.Log
import org.signal.core.util.readToList
import org.signal.core.util.requireNonNullString
/**
* Changes needed for edit message. New foreign keys require recreating the table.
*/
@Suppress("ClassName")
object V186_AddEditMessageColumnsMigration : SignalDatabaseMigration {
private val TAG = Log.tag(V186_AddEditMessageColumnsMigration::class.java)
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
val stopwatch = Stopwatch("migration")
val dependentItems: List<SqlItem> = getAllDependentItems(db, "message")
dependentItems.forEach { item ->
val sql = "DROP ${item.type} IF EXISTS ${item.name}"
Log.d(TAG, "Executing: $sql")
db.execSQL(sql)
}
stopwatch.split("drop-dependents")
db.execSQL(
"""
CREATE TABLE message_tmp (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
date_sent INTEGER NOT NULL,
date_received INTEGER NOT NULL,
date_server INTEGER DEFAULT -1,
thread_id INTEGER NOT NULL REFERENCES thread (_id) ON DELETE CASCADE,
from_recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
from_device_id INTEGER,
to_recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
type INTEGER NOT NULL,
body TEXT,
read INTEGER DEFAULT 0,
ct_l TEXT,
exp INTEGER,
m_type INTEGER,
m_size INTEGER,
st INTEGER,
tr_id TEXT,
subscription_id INTEGER DEFAULT -1,
receipt_timestamp INTEGER DEFAULT -1,
delivery_receipt_count INTEGER DEFAULT 0,
read_receipt_count INTEGER DEFAULT 0,
viewed_receipt_count INTEGER DEFAULT 0,
mismatched_identities TEXT DEFAULT NULL,
network_failures TEXT DEFAULT NULL,
expires_in INTEGER DEFAULT 0,
expire_started INTEGER DEFAULT 0,
notified INTEGER DEFAULT 0,
quote_id INTEGER DEFAULT 0,
quote_author INTEGER DEFAULT 0,
quote_body TEXT DEFAULT NULL,
quote_missing INTEGER DEFAULT 0,
quote_mentions BLOB DEFAULT NULL,
quote_type INTEGER DEFAULT 0,
shared_contacts TEXT DEFAULT NULL,
unidentified INTEGER DEFAULT 0,
link_previews TEXT DEFAULT NULL,
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,
message_ranges BLOB DEFAULT NULL,
story_type INTEGER DEFAULT 0,
parent_story_id INTEGER DEFAULT 0,
export_state BLOB DEFAULT NULL,
exported INTEGER DEFAULT 0,
scheduled_date INTEGER DEFAULT -1,
latest_revision_id INTEGER DEFAULT NULL REFERENCES message (_id) ON DELETE CASCADE,
original_message_id INTEGER DEFAULT NULL REFERENCES message (_id) ON DELETE CASCADE,
revision_number INTEGER DEFAULT 0
)
"""
)
stopwatch.split("create-table")
db.execSQL(
"""
INSERT INTO message_tmp
SELECT
_id,
date_sent,
date_received,
date_server,
thread_id,
from_recipient_id,
from_device_id,
to_recipient_id,
type,
body,
read,
ct_l,
exp,
m_type,
m_size,
st,
tr_id,
subscription_id,
receipt_timestamp,
delivery_receipt_count,
read_receipt_count,
viewed_receipt_count,
mismatched_identities,
network_failures,
expires_in,
expire_started,
notified,
quote_id,
quote_author,
quote_body,
quote_missing,
quote_mentions,
quote_type,
shared_contacts,
unidentified,
link_previews,
view_once,
reactions_unread,
reactions_last_seen,
remote_deleted,
mentions_self,
notified_timestamp,
server_guid,
message_ranges,
story_type,
parent_story_id,
export_state,
exported,
scheduled_date,
NULL AS latest_revision_id,
NULL AS original_message_id,
0 as revision_number
FROM message
"""
)
stopwatch.split("copy-data")
db.execSQL("DROP TABLE message")
stopwatch.split("drop-old")
db.execSQL("ALTER TABLE message_tmp RENAME TO message")
stopwatch.split("rename-table")
dependentItems.forEach { item ->
val sql = when (item.name) {
"message_thread_story_parent_story_scheduled_date_index" -> "CREATE INDEX message_thread_story_parent_story_scheduled_date_latest_revision_id_index ON message (thread_id, date_received, story_type, parent_story_id, scheduled_date, latest_revision_id)"
"message_quote_id_quote_author_scheduled_date_index" -> "CREATE INDEX message_quote_id_quote_author_scheduled_date_latest_revision_id_index ON message (quote_id, quote_author, scheduled_date, latest_revision_id)"
else -> item.createStatement
}
Log.d(TAG, "Executing: $sql")
db.execSQL(sql)
}
stopwatch.split("recreate-dependents")
db.execSQL("PRAGMA foreign_key_check")
stopwatch.split("fk-check")
stopwatch.stop(TAG)
}
private fun getAllDependentItems(db: SQLiteDatabase, tableName: String): List<SqlItem> {
return db.rawQuery("SELECT type, name, sql FROM sqlite_schema WHERE tbl_name='$tableName' AND type != 'table'").readToList { cursor ->
SqlItem(
type = cursor.requireNonNullString("type"),
name = cursor.requireNonNullString("name"),
createStatement = cursor.requireNonNullString("sql")
)
}
}
data class SqlItem(
val type: String,
val name: String,
val createStatement: String
)
}

View File

@@ -54,7 +54,9 @@ public class InMemoryMessageRecord extends MessageRecord {
false,
0,
0,
-1);
-1,
null,
0);
}
@Override

View File

@@ -70,6 +70,7 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
private final Payment payment;
private final CallTable.Call call;
private final long scheduledDate;
private final MessageId latestRevisionId;
public MediaMmsMessageRecord(long id,
Recipient fromRecipient,
@@ -106,18 +107,22 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
@Nullable GiftBadge giftBadge,
@Nullable Payment payment,
@Nullable CallTable.Call call,
long scheduledDate)
long scheduledDate,
@Nullable MessageId latestRevisionId,
@Nullable MessageId originalMessageId,
int revisionNumber)
{
super(id, body, fromRecipient, fromDeviceId, toRecipient, dateSent,
dateReceived, dateServer, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
subscriptionId, expiresIn, expireStarted, viewOnce, slideDeck,
readReceiptCount, quote, contacts, linkPreviews, unidentified, reactions, remoteDelete, notifiedTimestamp, viewedReceiptCount, receiptTimestamp,
storyType, parentStoryId, giftBadge);
this.mentionsSelf = mentionsSelf;
this.messageRanges = messageRanges;
this.payment = payment;
this.call = call;
this.scheduledDate = scheduledDate;
storyType, parentStoryId, giftBadge, originalMessageId, revisionNumber);
this.mentionsSelf = mentionsSelf;
this.messageRanges = messageRanges;
this.payment = payment;
this.call = call;
this.scheduledDate = scheduledDate;
this.latestRevisionId = latestRevisionId;
}
@Override
@@ -204,18 +209,24 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
return scheduledDate;
}
public @Nullable MessageId getLatestRevisionId() {
return latestRevisionId;
}
public @NonNull MediaMmsMessageRecord withReactions(@NonNull List<ReactionRecord> reactions) {
return new MediaMmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber());
}
public @NonNull MediaMmsMessageRecord withoutQuote() {
return new MediaMmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), null, getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber());
}
public @NonNull MediaMmsMessageRecord withAttachments(@NonNull Context context, @NonNull List<DatabaseAttachment> attachments) {
@@ -236,14 +247,16 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
return new MediaMmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), slideDeck,
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber());
}
public @NonNull MediaMmsMessageRecord withPayment(@NonNull Payment payment) {
return new MediaMmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment, getCall(), getScheduledDate());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment, getCall(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber());
}
@@ -251,7 +264,8 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
return new MediaMmsMessageRecord(getId(), getFromRecipient(), getFromDeviceId(), getToRecipient(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), getSlideDeck(),
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
getReadReceiptCount(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), call, getScheduledDate());
getNotifiedTimestamp(), getViewedReceiptCount(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), call, getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber());
}
private static @NonNull List<Contact> updateContacts(@NonNull List<Contact> contacts, @NonNull Map<AttachmentId, DatabaseAttachment> attachmentIdMap) {

View File

@@ -102,6 +102,8 @@ public abstract class MessageRecord extends DisplayRecord {
private final boolean remoteDelete;
private final long notifiedTimestamp;
private final long receiptTimestamp;
private final MessageId originalMessageId;
private final int revisionNumber;
protected Boolean isJumboji = null;
@@ -110,10 +112,18 @@ public abstract class MessageRecord extends DisplayRecord {
int deliveryStatus, int deliveryReceiptCount, long type,
Set<IdentityKeyMismatch> mismatches,
Set<NetworkFailure> networkFailures,
int subscriptionId, long expiresIn, long expireStarted,
int readReceiptCount, boolean unidentified,
@NonNull List<ReactionRecord> reactions, boolean remoteDelete, long notifiedTimestamp,
int viewedReceiptCount, long receiptTimestamp)
int subscriptionId,
long expiresIn,
long expireStarted,
int readReceiptCount,
boolean unidentified,
@NonNull List<ReactionRecord> reactions,
boolean remoteDelete,
long notifiedTimestamp,
int viewedReceiptCount,
long receiptTimestamp,
@Nullable MessageId originalMessageId,
int revisionNumber)
{
super(body, fromRecipient, toRecipient, dateSent, dateReceived,
threadId, deliveryStatus, deliveryReceiptCount, type,
@@ -131,6 +141,8 @@ public abstract class MessageRecord extends DisplayRecord {
this.remoteDelete = remoteDelete;
this.notifiedTimestamp = notifiedTimestamp;
this.receiptTimestamp = receiptTimestamp;
this.originalMessageId = originalMessageId;
this.revisionNumber = revisionNumber;
}
public abstract boolean isMms();
@@ -721,6 +733,18 @@ public abstract class MessageRecord extends DisplayRecord {
throw new NullPointerException();
}
public boolean isEditMessage() {
return originalMessageId != null;
}
public @Nullable MessageId getOriginalMessageId() {
return originalMessageId;
}
public int getRevisionNumber() {
return revisionNumber;
}
public static final class InviteAddState {
private final boolean invited;

View File

@@ -39,12 +39,13 @@ public abstract class MmsMessageRecord extends MessageRecord {
@NonNull List<LinkPreview> linkPreviews, boolean unidentified,
@NonNull List<ReactionRecord> reactions, boolean remoteDelete, long notifiedTimestamp,
int viewedReceiptCount, long receiptTimestamp, @NonNull StoryType storyType,
@Nullable ParentStoryId parentStoryId, @Nullable GiftBadge giftBadge)
@Nullable ParentStoryId parentStoryId, @Nullable GiftBadge giftBadge, @Nullable MessageId originalMessageId,
int revisionNumber)
{
super(id, body, fromRecipient, fromDeviceId, toRecipient,
dateSent, dateReceived, dateServer, threadId, deliveryStatus, deliveryReceiptCount,
type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount,
unidentified, reactions, remoteDelete, notifiedTimestamp, viewedReceiptCount, receiptTimestamp);
unidentified, reactions, remoteDelete, notifiedTimestamp, viewedReceiptCount, receiptTimestamp, originalMessageId, revisionNumber);
this.slideDeck = slideDeck;
this.quote = quote;

View File

@@ -60,7 +60,7 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord {
dateSent, dateReceived, -1, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox,
new HashSet<>(), new HashSet<>(), subscriptionId,
0, 0, false, slideDeck, readReceiptCount, null, Collections.emptyList(), Collections.emptyList(), false,
Collections.emptyList(), false, 0, viewedReceiptCount, receiptTimestamp, storyType, parentStoryId, giftBadge);
Collections.emptyList(), false, 0, viewedReceiptCount, receiptTimestamp, storyType, parentStoryId, giftBadge, null, 0);
this.contentLocation = contentLocation;
this.messageSize = messageSize;