diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_02.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_02.binproto index 5691f6ca63..be204779f4 100644 Binary files a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_02.binproto and b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_02.binproto differ diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_03.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_03.binproto index df6e214382..b3e1d07efd 100644 Binary files a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_03.binproto and b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_03.binproto differ diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_07.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_07.binproto index 9ec54377af..02d46a7394 100644 Binary files a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_07.binproto and b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_07.binproto differ diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_08.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_08.binproto index b14a7ecb92..b65d4b2f69 100644 Binary files a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_08.binproto and b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_08.binproto differ diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_09.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_09.binproto index 45832631c2..3e3b2680b5 100644 Binary files a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_09.binproto and b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_09.binproto differ diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_10.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_10.binproto index 71453500a6..8e143d6f75 100644 Binary files a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_10.binproto and b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_10.binproto differ diff --git a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_11.binproto b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_11.binproto index aa5ffa453c..c38e6ba059 100644 Binary files a/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_11.binproto and b/app/src/androidTest/assets/backupTests/chat_item_standard_message_with_quote_11.binproto differ diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest.kt index ff63c75b5e..2f356f8bc6 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest.kt @@ -388,6 +388,7 @@ class AttachmentTableTest { stickerLocator = null, gif = false, quote = false, + quoteTargetContentType = null, uuid = UUID.randomUUID(), fileName = null ) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest_deduping.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest_deduping.kt index c113b0f036..e89ae39010 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest_deduping.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/AttachmentTableTest_deduping.kt @@ -670,7 +670,9 @@ class AttachmentTableTest_deduping { caption = null, stickerLocator = null, blurHash = null, - uuid = UUID.randomUUID() + uuid = UUID.randomUUID(), + quote = false, + quoteTargetContentType = null ) ), quoteAttachment = emptyList() diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/UriAttachmentBuilder.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/UriAttachmentBuilder.kt index a51693a2ee..431c803609 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/UriAttachmentBuilder.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/UriAttachmentBuilder.kt @@ -19,6 +19,7 @@ object UriAttachmentBuilder { borderless: Boolean = false, videoGif: Boolean = false, quote: Boolean = false, + quoteTargetContentType: String? = null, caption: String? = null, stickerLocator: StickerLocator? = null, blurHash: BlurHash? = null, @@ -39,6 +40,7 @@ object UriAttachmentBuilder { borderless = borderless, videoGif = videoGif, quote = quote, + quoteTargetContentType = quoteTargetContentType, caption = caption, stickerLocator = stickerLocator, blurHash = blurHash, diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/SyncMessageProcessorTest_synchronizeDeleteForMe.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/SyncMessageProcessorTest_synchronizeDeleteForMe.kt index ea215371fa..8cf2a04d4f 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/SyncMessageProcessorTest_synchronizeDeleteForMe.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/SyncMessageProcessorTest_synchronizeDeleteForMe.kt @@ -705,7 +705,8 @@ class SyncMessageProcessorTest_synchronizeDeleteForMe { archiveCdn = this.archiveCdn, thumbnailRestoreState = this.thumbnailRestoreState, archiveTransferState = this.archiveTransferState, - uuid = uuid + uuid = uuid, + quoteTargetContentType = this.quoteTargetContentType ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/ArchivedAttachment.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/ArchivedAttachment.kt index 8f725ad361..c107a42c73 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/ArchivedAttachment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/ArchivedAttachment.kt @@ -45,11 +45,13 @@ class ArchivedAttachment : Attachment { stickerLocator: StickerLocator?, gif: Boolean, quote: Boolean, + quoteTargetContentType: String?, uuid: UUID?, fileName: String? ) : super( contentType = contentType ?: "", quote = quote, + quoteTargetContentType = quoteTargetContentType, transferState = AttachmentTable.TRANSFER_NEEDS_RESTORE, size = size, fileName = fileName, diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.kt index 291298d781..884950d05c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.kt @@ -59,6 +59,8 @@ abstract class Attachment( @JvmField val quote: Boolean, @JvmField + val quoteTargetContentType: String?, + @JvmField val uploadTimestamp: Long, @JvmField val caption: String?, @@ -98,6 +100,7 @@ abstract class Attachment( height = parcel.readInt(), incrementalMacChunkSize = parcel.readInt(), quote = ParcelUtil.readBoolean(parcel), + quoteTargetContentType = parcel.readString(), uploadTimestamp = parcel.readLong(), caption = parcel.readString(), stickerLocator = ParcelCompat.readParcelable(parcel, StickerLocator::class.java.classLoader, StickerLocator::class.java), @@ -126,6 +129,7 @@ abstract class Attachment( dest.writeInt(height) dest.writeInt(incrementalMacChunkSize) ParcelUtil.writeBoolean(dest, quote) + dest.writeString(quoteTargetContentType) dest.writeLong(uploadTimestamp) dest.writeString(caption) dest.writeParcelable(stickerLocator, 0) diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.kt index 313e06438a..b88e44e316 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachment.kt @@ -75,7 +75,8 @@ class DatabaseAttachment : Attachment { archiveCdn: Int?, thumbnailRestoreState: AttachmentTable.ThumbnailRestoreState, archiveTransferState: AttachmentTable.ArchiveTransferState, - uuid: UUID? + uuid: UUID?, + quoteTargetContentType: String? ) : super( contentType = contentType, transferState = transferProgress, @@ -93,6 +94,7 @@ class DatabaseAttachment : Attachment { height = height, incrementalMacChunkSize = incrementalMacChunkSize, quote = quote, + quoteTargetContentType = quoteTargetContentType, uploadTimestamp = uploadTimestamp, caption = caption, stickerLocator = stickerLocator, diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/LocalStickerAttachment.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/LocalStickerAttachment.kt index 944ec9e2aa..e1533618fb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/LocalStickerAttachment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/LocalStickerAttachment.kt @@ -40,6 +40,7 @@ class LocalStickerAttachment : Attachment { height = StickerSlide.HEIGHT, incrementalMacChunkSize = 0, quote = false, + quoteTargetContentType = null, uploadTimestamp = 0, caption = null, stickerLocator = stickerLocator, diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.kt index 7c14cb93be..d46916566b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/PointerAttachment.kt @@ -38,7 +38,9 @@ class PointerAttachment : Attachment { caption: String?, stickerLocator: StickerLocator?, blurHash: BlurHash?, - uuid: UUID? + uuid: UUID?, + quote: Boolean, + quoteTargetContentType: String? = null ) : super( contentType = contentType, transferState = transferState, @@ -56,7 +58,8 @@ class PointerAttachment : Attachment { width = width, height = height, incrementalMacChunkSize = incrementalMacChunkSize, - quote = false, + quote = quote, + quoteTargetContentType = quoteTargetContentType, uploadTimestamp = uploadTimestamp, caption = caption, stickerLocator = stickerLocator, @@ -91,7 +94,9 @@ class PointerAttachment : Attachment { pointer: Optional, stickerLocator: StickerLocator? = null, fastPreflightId: String? = null, - transferState: Int = AttachmentTable.TRANSFER_PROGRESS_PENDING + transferState: Int = AttachmentTable.TRANSFER_PROGRESS_PENDING, + quote: Boolean = false, + quoteTargetContentType: String? = null ): Optional { if (!pointer.isPresent || !pointer.get().isPointer()) { return Optional.empty() @@ -122,7 +127,9 @@ class PointerAttachment : Attachment { caption = pointer.get().asPointer().caption.orElse(null), stickerLocator = stickerLocator, blurHash = BlurHash.parseOrNull(pointer.get().asPointer().blurHash.orElse(null)), - uuid = pointer.get().asPointer().uuid + uuid = pointer.get().asPointer().uuid, + quote = quote, + quoteTargetContentType = quoteTargetContentType ) ) } @@ -140,7 +147,9 @@ class PointerAttachment : Attachment { return Optional.of( PointerAttachment( - contentType = quotedAttachment.contentType!!, + quote = true, + contentType = quotedAttachment.thumbnail?.contentType, + quoteTargetContentType = quotedAttachment.contentType!!, transferState = AttachmentTable.TRANSFER_PROGRESS_PENDING, size = (if (thumbnail != null) thumbnail.asPointer().size.orElse(0) else 0).toLong(), fileName = quotedAttachment.fileName, diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.kt index 8c4cb92a49..aee79af34b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/TombstoneAttachment.kt @@ -5,6 +5,7 @@ import android.os.Parcel import org.thoughtcrime.securesms.blurhash.BlurHash import org.thoughtcrime.securesms.database.AttachmentTable import org.thoughtcrime.securesms.stickers.StickerLocator +import org.thoughtcrime.securesms.util.MediaUtil import java.util.UUID /** @@ -14,9 +15,21 @@ import java.util.UUID * quote them and know their contentType even though the media has been deleted. */ class TombstoneAttachment : Attachment { - constructor(contentType: String?, quote: Boolean) : super( + + companion object { + fun forQuote(): TombstoneAttachment { + return TombstoneAttachment(contentType = null, quote = true, quoteTargetContentType = MediaUtil.VIEW_ONCE) + } + + fun forNonQuote(contentType: String?): TombstoneAttachment { + return TombstoneAttachment(contentType = contentType, quote = false, quoteTargetContentType = null) + } + } + + constructor(contentType: String?, quote: Boolean, quoteTargetContentType: String?) : super( contentType = contentType, quote = quote, + quoteTargetContentType = quoteTargetContentType, transferState = AttachmentTable.TRANSFER_PROGRESS_DONE, size = 0, fileName = null, @@ -55,10 +68,12 @@ class TombstoneAttachment : Attachment { gif: Boolean = false, stickerLocator: StickerLocator? = null, quote: Boolean, + quoteTargetContentType: String?, uuid: UUID? ) : super( contentType = contentType ?: "", quote = quote, + quoteTargetContentType = quoteTargetContentType, transferState = AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE, size = 0, fileName = fileName, diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.kt index 18439eb2e1..5befd1f956 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/UriAttachment.kt @@ -22,6 +22,7 @@ class UriAttachment : Attachment { borderless: Boolean, videoGif: Boolean, quote: Boolean, + quoteTargetContentType: String?, caption: String?, stickerLocator: StickerLocator?, blurHash: BlurHash?, @@ -40,6 +41,7 @@ class UriAttachment : Attachment { borderless = borderless, videoGif = videoGif, quote = quote, + quoteTargetContentType = quoteTargetContentType, caption = caption, stickerLocator = stickerLocator, blurHash = blurHash, @@ -61,6 +63,7 @@ class UriAttachment : Attachment { borderless: Boolean, videoGif: Boolean, quote: Boolean, + quoteTargetContentType: String?, caption: String?, stickerLocator: StickerLocator?, blurHash: BlurHash?, @@ -85,6 +88,7 @@ class UriAttachment : Attachment { height = height, incrementalMacChunkSize = 0, quote = quote, + quoteTargetContentType = quoteTargetContentType, uploadTimestamp = 0, caption = caption, stickerLocator = stickerLocator, diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/WallpaperAttachment.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/WallpaperAttachment.kt index c6e4d417c9..a219b88d98 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/WallpaperAttachment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/WallpaperAttachment.kt @@ -30,6 +30,7 @@ class WallpaperAttachment() : Attachment( height = 0, incrementalMacChunkSize = 0, quote = false, + quoteTargetContentType = null, uploadTimestamp = 0, caption = null, stickerLocator = null, diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt index bbcbdedb58..96e606150d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt @@ -1071,7 +1071,7 @@ private fun BackupMessageRecord.toRemoteQuote(exportState: ExportState, attachme val localType = QuoteModel.Type.fromCode(this.quoteType) val remoteType = when (localType) { QuoteModel.Type.NORMAL -> { - if (attachments?.any { it.contentType == MediaUtil.VIEW_ONCE } == true) { + if (attachments?.any { it.quoteTargetContentType == MediaUtil.VIEW_ONCE } == true) { Quote.Type.VIEW_ONCE } else { Quote.Type.NORMAL @@ -1158,11 +1158,11 @@ private fun DatabaseAttachment.toRemoteStickerMessage(sentTimestamp: Long, react private fun List.toRemoteQuoteAttachments(): List { return this.map { attachment -> Quote.QuotedAttachment( - contentType = attachment.contentType, + contentType = attachment.quoteTargetContentType, fileName = attachment.fileName, thumbnail = attachment.toRemoteMessageAttachment( flagOverride = MessageAttachment.Flag.NONE, - contentTypeOverride = "image/jpeg" + contentTypeOverride = attachment.contentType ) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt index 6b311698b5..97c398b9b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt @@ -76,7 +76,6 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.stickers.StickerLocator import org.thoughtcrime.securesms.util.JsonUtils -import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.MessageUtil import org.whispersystems.signalservice.api.payments.Money import org.whispersystems.signalservice.api.push.ServiceId @@ -1059,8 +1058,8 @@ class ChatItemArchiveImporter( else -> null } }, - start = bodyRange.start ?: 0, - length = bodyRange.length ?: 0 + start = bodyRange.start, + length = bodyRange.length ) } ) @@ -1090,11 +1089,11 @@ class ChatItemArchiveImporter( private fun Quote.toLocalAttachments(): List { if (this.type == Quote.Type.VIEW_ONCE) { - return listOf(TombstoneAttachment(contentType = MediaUtil.VIEW_ONCE, quote = true)) + return listOf(TombstoneAttachment.forQuote()) } - return attachments.mapNotNull { attachment -> - val thumbnail = attachment.thumbnail?.toLocalAttachment(quote = true) + return this.attachments.mapNotNull { attachment -> + val thumbnail = attachment.thumbnail?.toLocalAttachment(quote = true, quoteTargetContentType = attachment.contentType) if (thumbnail != null) { return@mapNotNull thumbnail @@ -1141,7 +1140,7 @@ class ChatItemArchiveImporter( ) } - private fun MessageAttachment.toLocalAttachment(quote: Boolean = false, contentType: String? = pointer?.contentType): Attachment? { + private fun MessageAttachment.toLocalAttachment(quote: Boolean = false, quoteTargetContentType: String? = null, contentType: String? = pointer?.contentType): Attachment? { return pointer?.toLocalAttachment( voiceNote = flag == MessageAttachment.Flag.VOICE_MESSAGE, borderless = flag == MessageAttachment.Flag.BORDERLESS, @@ -1150,7 +1149,8 @@ class ChatItemArchiveImporter( contentType = contentType, fileName = pointer.fileName, uuid = clientUuid, - quote = quote + quote = quote, + quoteTargetContentType = quoteTargetContentType ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/util/ArchiveConverterExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/util/ArchiveConverterExtensions.kt index d8d5d5a703..135ebf13a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/util/ArchiveConverterExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/util/ArchiveConverterExtensions.kt @@ -39,7 +39,8 @@ fun FilePointer?.toLocalAttachment( contentType: String? = this?.contentType, fileName: String? = this?.fileName, uuid: ByteString? = null, - quote: Boolean = false + quote: Boolean = false, + quoteTargetContentType: String? = null ): Attachment? { if (this == null || this.locatorInfo == null) return null @@ -71,6 +72,7 @@ fun FilePointer?.toLocalAttachment( stickerLocator = stickerLocator, gif = gif, quote = quote, + quoteTargetContentType = quoteTargetContentType, uuid = UuidUtil.fromByteStringOrNull(uuid), fileName = fileName ) @@ -100,7 +102,9 @@ fun FilePointer?.toLocalAttachment( PointerAttachment.forPointer( pointer = Optional.of(signalAttachmentPointer), stickerLocator = stickerLocator, - transferState = if (wasDownloaded) AttachmentTable.TRANSFER_NEEDS_RESTORE else AttachmentTable.TRANSFER_PROGRESS_PENDING + transferState = if (wasDownloaded) AttachmentTable.TRANSFER_NEEDS_RESTORE else AttachmentTable.TRANSFER_PROGRESS_PENDING, + quote = quote, + quoteTargetContentType = quoteTargetContentType ).orNull() } AttachmentType.INVALID -> { @@ -117,6 +121,7 @@ fun FilePointer?.toLocalAttachment( borderless = borderless, gif = gif, quote = quote, + quoteTargetContentType = quoteTargetContentType, stickerLocator = stickerLocator, uuid = UuidUtil.fromByteStringOrNull(uuid) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java b/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java index 06826de0e6..2da4700a35 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java @@ -219,7 +219,7 @@ public class InputPanel extends ConstraintLayout @NonNull SlideDeck attachments, @NonNull QuoteModel.Type quoteType) { - this.quoteView.setQuote(requestManager, id, author, body, false, attachments, null, quoteType); + this.quoteView.setQuote(requestManager, id, author, body, false, attachments, null, quoteType, true); if (listener != null) { this.quoteView.setOnClickListener(v -> listener.onQuoteClicked(id, author.getId())); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java b/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java index d31715a1ad..79fd50eb4a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java @@ -47,6 +47,7 @@ import org.thoughtcrime.securesms.util.views.Stub; import java.io.IOException; import java.util.List; +import java.util.Optional; public class QuoteView extends ConstraintLayout implements RecipientForeverObserver { @@ -200,7 +201,8 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser boolean originalMissing, @NonNull SlideDeck attachments, @Nullable String storyReaction, - @NonNull QuoteModel.Type quoteType) + @NonNull QuoteModel.Type quoteType, + boolean composeMode) { if (this.author != null) this.author.removeForeverObserver(this); @@ -211,9 +213,19 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser this.quoteType = quoteType; this.author.observeForever(this); + + Slide slide = attachments.getFirstSlide(); + + String quoteTargetContentType; + if (composeMode) { + quoteTargetContentType = Optional.ofNullable(slide).map(Slide::getContentType).orElse(null); + } else { + quoteTargetContentType = Optional.ofNullable(slide).map(Slide::getQuoteTargetContentType).orElse(null); + } + setQuoteAuthor(author); - setQuoteText(resolveBody(body, quoteType), attachments, originalMissing, storyReaction); - setQuoteAttachment(requestManager, body, attachments, originalMissing); + setQuoteText(resolveBody(body, quoteType), slide, originalMissing, storyReaction, quoteTargetContentType); + setQuoteAttachment(requestManager, body, slide, originalMissing, quoteTargetContentType); setQuoteMissingFooter(originalMissing); applyColorTheme(); } @@ -267,9 +279,10 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser } private void setQuoteText(@Nullable CharSequence body, - @NonNull SlideDeck attachments, + @Nullable Slide slide, boolean originalMissing, - @Nullable String storyReaction) + @Nullable String storyReaction, + @Nullable String quoteTargetContentType) { if (originalMissing && isStoryReply()) { bodyView.setVisibility(GONE); @@ -316,40 +329,37 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser bodyView.setVisibility(GONE); mediaDescriptionText.setVisibility(VISIBLE); - Slide audioSlide = attachments.getSlides().stream().filter(Slide::hasAudio).findFirst().orElse(null); - Slide documentSlide = attachments.getSlides().stream().filter(Slide::hasDocument).findFirst().orElse(null); - Slide imageSlide = attachments.getSlides().stream().filter(Slide::hasImage).findFirst().orElse(null); - Slide videoSlide = attachments.getSlides().stream().filter(Slide::hasVideo).findFirst().orElse(null); - Slide stickerSlide = attachments.getSlides().stream().filter(Slide::hasSticker).findFirst().orElse(null); - Slide viewOnceSlide = attachments.getSlides().stream().filter(Slide::hasViewOnce).findFirst().orElse(null); - - // Given that most types have images, we specifically check images last - if (viewOnceSlide != null) { + if (MediaUtil.isViewOnceType(quoteTargetContentType)) { mediaDescriptionText.setPadding(0, mediaDescriptionText.getPaddingTop(), 0, (int) DimensionUnit.DP.toPixels(8)); mediaDescriptionText.setText(R.string.QuoteView_view_once_media); - } else if (audioSlide != null) { + } else if (MediaUtil.isAudioType(quoteTargetContentType)) { mediaDescriptionText.setPadding(0, mediaDescriptionText.getPaddingTop(), 0, (int) DimensionUnit.DP.toPixels(8)); mediaDescriptionText.setText(R.string.QuoteView_audio); - } else if (documentSlide != null) { - mediaDescriptionText.setVisibility(GONE); - } else if (videoSlide != null) { - if (videoSlide.isVideoGif()) { + } else if (MediaUtil.isVideoType(quoteTargetContentType)) { + if (slide != null && slide.isVideoGif()) { mediaDescriptionText.setText(R.string.QuoteView_gif); } else { mediaDescriptionText.setText(R.string.QuoteView_video); } - } else if (stickerSlide != null) { + } else if (slide != null && slide.hasSticker()) { mediaDescriptionText.setText(R.string.QuoteView_sticker); - } else if (imageSlide != null) { - if (MediaUtil.isGif(imageSlide.getContentType())) { + } else if (MediaUtil.isImageType(quoteTargetContentType)) { + if (MediaUtil.isGif(quoteTargetContentType)) { mediaDescriptionText.setText(R.string.QuoteView_gif); } else { mediaDescriptionText.setText(R.string.QuoteView_photo); } + } else { + mediaDescriptionText.setVisibility(GONE); } } - private void setQuoteAttachment(@NonNull RequestManager requestManager, @NonNull CharSequence body, @NonNull SlideDeck slideDeck, boolean originalMissing) { + private void setQuoteAttachment(@NonNull RequestManager requestManager, + @NonNull CharSequence body, + @NonNull Slide slide, + boolean originalMissing, + @Nullable String quoteTargetContentType) + { boolean outgoing = messageType != MessageType.INCOMING && messageType != MessageType.STORY_REPLY_INCOMING; boolean preview = messageType == MessageType.PREVIEW || messageType == MessageType.STORY_REPLY_PREVIEW; @@ -394,41 +404,49 @@ public class QuoteView extends ConstraintLayout implements RecipientForeverObser return; } - Slide imageVideoSlide = slideDeck.getSlides().stream().filter(s -> s.hasImage() || s.hasVideo() || s.hasSticker()).findFirst().orElse(null); - Slide documentSlide = slideDeck.getSlides().stream().filter(Slide::hasDocument).findFirst().orElse(null); - Slide viewOnceSlide = slideDeck.getSlides().stream().filter(Slide::hasViewOnce).findFirst().orElse(null); - - attachmentVideoOVerlayStub.setVisibility(GONE); - - if (viewOnceSlide != null) { - thumbnailView.setVisibility(GONE); - attachmentNameViewStub.setVisibility(GONE); - } else if (imageVideoSlide != null && imageVideoSlide.getUri() != null) { - thumbnailView.setVisibility(VISIBLE); - attachmentNameViewStub.setVisibility(GONE); - - if (dismissStub.resolved()) { - dismissStub.get().setBackgroundResource(R.drawable.dismiss_background); - } - if (imageVideoSlide.hasVideo() && !imageVideoSlide.isVideoGif()) { - attachmentVideoOVerlayStub.setVisibility(VISIBLE); - } - requestManager.load(new DecryptableUri(imageVideoSlide.getUri())) - .centerCrop() - .override(thumbWidth, thumbHeight) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .into(thumbnailView); - } else if (documentSlide != null){ - thumbnailView.setVisibility(GONE); - attachmentNameViewStub.setVisibility(VISIBLE); - attachmentNameViewStub.get().setText(documentSlide.getFileName().orElse("")); - } else { + if (TextUtils.isEmpty(quoteTargetContentType) || slide == null || slide.getUri() == null) { thumbnailView.setVisibility(GONE); attachmentNameViewStub.setVisibility(GONE); if (dismissStub.resolved()) { dismissStub.get().setBackground(null); } + return; + } + + attachmentVideoOVerlayStub.setVisibility(GONE); + + if (MediaUtil.isViewOnceType(quoteTargetContentType)) { + thumbnailView.setVisibility(GONE); + attachmentNameViewStub.setVisibility(GONE); + } else if (MediaUtil.isImageOrVideoType(quoteTargetContentType)) { + thumbnailView.setVisibility(VISIBLE); + attachmentNameViewStub.setVisibility(GONE); + + if (dismissStub.resolved()) { + dismissStub.get().setBackgroundResource(R.drawable.dismiss_background); + } + + if (MediaUtil.isVideoType(quoteTargetContentType) && !slide.isVideoGif()) { + attachmentVideoOVerlayStub.setVisibility(VISIBLE); + } + + requestManager.load(new DecryptableUri(slide.getUri())) + .centerCrop() + .override(thumbWidth, thumbHeight) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .into(thumbnailView); + } else if (MediaUtil.isAudioType(quoteTargetContentType)) { + thumbnailView.setVisibility(GONE); + attachmentNameViewStub.setVisibility(GONE); + + if (dismissStub.resolved()) { + dismissStub.get().setBackground(null); + } + } else { + thumbnailView.setVisibility(GONE); + attachmentNameViewStub.setVisibility(VISIBLE); + attachmentNameViewStub.get().setText(slide.getFileName().orElse("")); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt index dbec26f3f4..cbcd25293f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/InternalConversationSettingsFragment.kt @@ -98,6 +98,7 @@ class InternalConversationSettingsFragment : ComposeFragment(), InternalConversa borderless = false, videoGif = false, quote = false, + quoteTargetContentType = null, caption = null, stickerLocator = null, blurHash = null, diff --git a/app/src/main/java/org/thoughtcrime/securesms/contactshare/Contact.java b/app/src/main/java/org/thoughtcrime/securesms/contactshare/Contact.java index 128482f429..e0990c7cd2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contactshare/Contact.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contactshare/Contact.java @@ -650,7 +650,7 @@ public class Contact implements Parcelable { private static Attachment attachmentFromUri(@Nullable Uri uri) { if (uri == null) return null; - return new UriAttachment(uri, MediaUtil.IMAGE_JPEG, AttachmentTable.TRANSFER_PROGRESS_DONE, 0, null, false, false, false, false, null, null, null, null, null); + return new UriAttachment(uri, MediaUtil.IMAGE_JPEG, AttachmentTable.TRANSFER_PROGRESS_DONE, 0, null, false, false, false, false, null, null, null, null, null, null); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index 4a8b2b180a..f9ba8715d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -1649,7 +1649,8 @@ public final class ConversationItem extends RelativeLayout implements BindableCo quote.isOriginalMissing(), quote.getAttachment(), isStoryReaction(current) ? current.getBody() : null, - quote.getQuoteType()); + quote.getQuoteType(), + false); quoteView.setWallpaperEnabled(hasWallpaper); quoteView.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt index 37a757c6ea..e0b6514e1f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt @@ -505,7 +505,7 @@ class ConversationRepository( } if (messageRecord.isViewOnceMessage()) { - val attachment = TombstoneAttachment(MediaUtil.VIEW_ONCE, true) + val attachment = TombstoneAttachment.forQuote() slideDeck = SlideDeck() slideDeck.addSlide(MediaUtil.getSlideForAttachment(attachment)) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemMediaViewHolder.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemMediaViewHolder.kt index 03ed78e844..079b65aa8a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemMediaViewHolder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemMediaViewHolder.kt @@ -88,7 +88,8 @@ class V2ConversationItemMediaViewHolder>( quote.isOriginalMissing, quote.attachment, if (conversationMessage.messageRecord.isStoryReaction()) conversationMessage.messageRecord.body else null, - quote.quoteType + quote.quoteType, + false ) quoteView.setMessageType( diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt index d93c935004..7705aa474d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt @@ -180,6 +180,7 @@ class AttachmentTable( const val THUMBNAIL_RESTORE_STATE = "thumbnail_restore_state" const val ATTACHMENT_UUID = "attachment_uuid" const val OFFLOAD_RESTORED_AT = "offload_restored_at" + const val QUOTE_TARGET_CONTENT_TYPE = "quote_target_content_type" const val ATTACHMENT_JSON_ALIAS = "attachment_json" @@ -217,6 +218,7 @@ class AttachmentTable( BORDERLESS, VIDEO_GIF, QUOTE, + QUOTE_TARGET_CONTENT_TYPE, WIDTH, HEIGHT, CAPTION, @@ -279,7 +281,8 @@ class AttachmentTable( $THUMBNAIL_RANDOM BLOB DEFAULT NULL, $THUMBNAIL_RESTORE_STATE INTEGER DEFAULT ${ThumbnailRestoreState.NONE.value}, $ATTACHMENT_UUID TEXT DEFAULT NULL, - $OFFLOAD_RESTORED_AT INTEGER DEFAULT 0 + $OFFLOAD_RESTORED_AT INTEGER DEFAULT 0, + $QUOTE_TARGET_CONTENT_TYPE TEXT DEFAULT NULL ) """ @@ -423,6 +426,13 @@ class AttachmentTable( .run() } + fun hasData(attachmentId: AttachmentId): Boolean { + return readableDatabase + .exists(TABLE_NAME) + .where("$ID = ? AND $DATA_FILE NOT NULL", attachmentId) + .run() + } + fun getAttachment(attachmentId: AttachmentId): DatabaseAttachment? { return readableDatabase .select(*PROJECTION) @@ -1790,9 +1800,9 @@ class AttachmentTable( for (attachment in attachments) { val attachmentId = when { attachment is LocalStickerAttachment -> insertLocalStickerAttachment(mmsId, attachment) - attachment.uri != null -> insertAttachmentWithData(mmsId, attachment, attachment.quote) - attachment is ArchivedAttachment -> insertArchivedAttachment(mmsId, attachment, attachment.quote) - else -> insertUndownloadedAttachment(mmsId, attachment, attachment.quote) + attachment.uri != null -> insertAttachmentWithData(mmsId, attachment) + attachment is ArchivedAttachment -> insertArchivedAttachment(mmsId, attachment, quote = false, quoteTargetContentType = null) + else -> insertUndownloadedAttachment(mmsId, attachment, quote = false) } insertedAttachments[attachment] = attachmentId @@ -1803,8 +1813,8 @@ class AttachmentTable( for (attachment in quoteAttachment) { val attachmentId = when { attachment.uri != null -> insertQuoteAttachment(mmsId, attachment) - attachment is ArchivedAttachment -> insertArchivedAttachment(mmsId, attachment, true) - else -> insertUndownloadedAttachment(mmsId, attachment, true) + attachment is ArchivedAttachment -> insertArchivedAttachment(mmsId, attachment, quote = true, quoteTargetContentType = attachment.quoteTargetContentType) + else -> insertUndownloadedAttachment(mmsId, attachment, quote = true) } insertedAttachments[attachment] = attachmentId @@ -2040,6 +2050,7 @@ class AttachmentTable( width = jsonObject.getInt(WIDTH), height = jsonObject.getInt(HEIGHT), quote = jsonObject.getInt(QUOTE) != 0, + quoteTargetContentType = if (!jsonObject.isNull(QUOTE_TARGET_CONTENT_TYPE)) jsonObject.getString(QUOTE_TARGET_CONTENT_TYPE) else null, caption = jsonObject.getString(CAPTION), stickerLocator = if (jsonObject.getInt(STICKER_ID) >= 0) { StickerLocator( @@ -2394,6 +2405,7 @@ class AttachmentTable( put(WIDTH, attachment.width) put(HEIGHT, attachment.height) put(QUOTE, quote.toInt()) + put(QUOTE_TARGET_CONTENT_TYPE, attachment.quoteTargetContentType) put(CAPTION, attachment.caption) put(UPLOAD_TIMESTAMP, attachment.uploadTimestamp) put(BLUR_HASH, attachment.blurHash?.hash) @@ -2425,6 +2437,8 @@ class AttachmentTable( /** * When inserting a quote attachment, it looks a lot like a normal attachment insert, but rather than insert the actual data pointed at by the attachment's * URI, we instead want to generate a thumbnail of that attachment and use that instead. + * + * It's important to note that it's assumed that [attachment] is the attachment that you're *quoting*. We'll use it's contentType as the quoteTargetContentType. */ @Throws(MmsException::class) private fun insertQuoteAttachment(messageId: Long, attachment: Attachment): AttachmentId { @@ -2438,7 +2452,8 @@ class AttachmentTable( messageId = messageId, dataStream = thumbnail.data.inputStream(), attachment = attachment, - quote = true + quote = true, + quoteTargetContentType = attachment.contentType ) } @@ -2446,7 +2461,7 @@ class AttachmentTable( val attachmentId: AttachmentId = writableDatabase.withinTransaction { db -> val contentValues = ContentValues().apply { put(MESSAGE_ID, messageId) - put(CONTENT_TYPE, attachment.contentType) + putNull(CONTENT_TYPE) put(VOICE_NOTE, attachment.voiceNote.toInt()) put(BORDERLESS, attachment.borderless.toInt()) put(VIDEO_GIF, attachment.videoGif.toInt()) @@ -2455,6 +2470,7 @@ class AttachmentTable( put(WIDTH, attachment.width) put(HEIGHT, attachment.height) put(QUOTE, 1) + put(QUOTE_TARGET_CONTENT_TYPE, attachment.contentType) put(BLUR_HASH, attachment.blurHash?.hash) put(FILE_NAME, attachment.fileName) @@ -2529,7 +2545,7 @@ class AttachmentTable( * Callers are expected to later call [finalizeAttachmentAfterDownload] once they have downloaded the data for this attachment. */ @Throws(MmsException::class) - private fun insertArchivedAttachment(messageId: Long, attachment: ArchivedAttachment, quote: Boolean): AttachmentId { + private fun insertArchivedAttachment(messageId: Long, attachment: ArchivedAttachment, quote: Boolean, quoteTargetContentType: String?): AttachmentId { Log.d(TAG, "[insertArchivedAttachment] Inserting attachment for messageId $messageId.") val attachmentId: AttachmentId = writableDatabase.withinTransaction { db -> @@ -2552,6 +2568,7 @@ class AttachmentTable( put(WIDTH, attachment.width) put(HEIGHT, attachment.height) put(QUOTE, quote.toInt()) + put(QUOTE_TARGET_CONTENT_TYPE, quoteTargetContentType) put(CAPTION, attachment.caption) put(UPLOAD_TIMESTAMP, attachment.uploadTimestamp) put(ARCHIVE_CDN, attachment.archiveCdn) @@ -2681,14 +2698,14 @@ class AttachmentTable( attachmentId = AttachmentId(rowId) } - return attachmentId + return attachmentId as AttachmentId } /** * Inserts an attachment with existing data. This is likely an outgoing attachment that we're in the process of sending. */ @Throws(MmsException::class) - private fun insertAttachmentWithData(messageId: Long, attachment: Attachment, quote: Boolean): AttachmentId { + private fun insertAttachmentWithData(messageId: Long, attachment: Attachment): AttachmentId { requireNotNull(attachment.uri) { "Attachment must have a uri!" } Log.d(TAG, "[insertAttachmentWithData] Inserting attachment for messageId $messageId. (MessageId: $messageId, ${attachment.uri})") @@ -2699,7 +2716,7 @@ class AttachmentTable( throw MmsException(e) } - return insertAttachmentWithData(messageId, dataStream, attachment, quote) + return insertAttachmentWithData(messageId, dataStream, attachment, quote = false, quoteTargetContentType = null) } /** @@ -2708,7 +2725,7 @@ class AttachmentTable( * @param dataStream The stream to read the data from. This stream will be closed by this method. */ @Throws(MmsException::class) - private fun insertAttachmentWithData(messageId: Long, dataStream: InputStream, attachment: Attachment, quote: Boolean): AttachmentId { + private fun insertAttachmentWithData(messageId: Long, dataStream: InputStream, attachment: Attachment, quote: Boolean, quoteTargetContentType: String?): AttachmentId { // To avoid performing long-running operations in a transaction, we write the data to an independent file first in a way that doesn't rely on db state. val fileWriteResult: DataFileWriteResult = writeToDataFile(newDataFile(context), dataStream, attachment.transformProperties ?: TransformProperties.empty()) Log.d(TAG, "[insertAttachmentWithData] Wrote data to file: ${fileWriteResult.file.absolutePath} (MessageId: $messageId, ${attachment.uri})") @@ -2808,6 +2825,7 @@ class AttachmentTable( contentValues.put(WIDTH, uploadTemplate?.width ?: attachment.width) contentValues.put(HEIGHT, uploadTemplate?.height ?: attachment.height) contentValues.put(QUOTE, quote.toInt()) + contentValues.put(QUOTE_TARGET_CONTENT_TYPE, quoteTargetContentType) contentValues.put(CAPTION, attachment.caption) contentValues.put(UPLOAD_TIMESTAMP, uploadTemplate?.uploadTimestamp ?: 0) contentValues.put(TRANSFORM_PROPERTIES, transformProperties.serialize()) @@ -2849,7 +2867,7 @@ class AttachmentTable( } fun insertWallpaper(dataStream: InputStream): AttachmentId { - return insertAttachmentWithData(WALLPAPER_MESSAGE_ID, dataStream, WallpaperAttachment(), quote = false).also { id -> + return insertAttachmentWithData(WALLPAPER_MESSAGE_ID, dataStream, WallpaperAttachment(), quote = false, quoteTargetContentType = null).also { id -> createRemoteKeyIfNecessary(id) } } @@ -2964,6 +2982,7 @@ class AttachmentTable( width = cursor.requireInt(WIDTH), height = cursor.requireInt(HEIGHT), quote = cursor.requireBoolean(QUOTE), + quoteTargetContentType = cursor.requireString(QUOTE_TARGET_CONTENT_TYPE), caption = cursor.requireString(CAPTION), stickerLocator = cursor.readStickerLocator(), blurHash = if (MediaUtil.isAudioType(contentType)) null else BlurHash.parseOrNull(cursor.requireString(BLUR_HASH)), @@ -3148,7 +3167,7 @@ class AttachmentTable( * disk savings. */ @Throws(Exception::class) - fun migrationFinalizeQuoteWithData(previousDataFile: String, thumbnail: ImageCompressionUtil.Result): String { + fun migrationFinalizeQuoteWithData(previousDataFile: String, thumbnail: ImageCompressionUtil.Result, quoteTargetContentType: String?): String { val newDataFileInfo = writeToDataFile(newDataFile(context), thumbnail.data.inputStream(), TransformProperties.empty()) writableDatabase @@ -3160,6 +3179,7 @@ class AttachmentTable( DATA_HASH_START to newDataFileInfo.hash, DATA_HASH_END to newDataFileInfo.hash, CONTENT_TYPE to thumbnail.mimeType, + QUOTE_TARGET_CONTENT_TYPE to quoteTargetContentType, WIDTH to thumbnail.width, HEIGHT to thumbnail.height, QUOTE to 1 diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt index f481cce185..7e1b597807 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -396,7 +396,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat '${AttachmentTable.ARCHIVE_CDN}', ${AttachmentTable.TABLE_NAME}.${AttachmentTable.ARCHIVE_CDN}, '${AttachmentTable.THUMBNAIL_RESTORE_STATE}', ${AttachmentTable.TABLE_NAME}.${AttachmentTable.THUMBNAIL_RESTORE_STATE}, '${AttachmentTable.ARCHIVE_TRANSFER_STATE}', ${AttachmentTable.TABLE_NAME}.${AttachmentTable.ARCHIVE_TRANSFER_STATE}, - '${AttachmentTable.ATTACHMENT_UUID}', ${AttachmentTable.TABLE_NAME}.${AttachmentTable.ATTACHMENT_UUID} + '${AttachmentTable.ATTACHMENT_UUID}', ${AttachmentTable.TABLE_NAME}.${AttachmentTable.ATTACHMENT_UUID}, + '${AttachmentTable.QUOTE_TARGET_CONTENT_TYPE}', ${AttachmentTable.TABLE_NAME}.${AttachmentTable.QUOTE_TARGET_CONTENT_TYPE} ) ) AS ${AttachmentTable.ATTACHMENT_JSON_ALIAS} """.toSingleLine() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt index 12e12996e0..f5e7766b5a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt @@ -143,6 +143,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V285_AddEpochToCall import org.thoughtcrime.securesms.database.helpers.migration.V286_FixRemoteKeyEncoding import org.thoughtcrime.securesms.database.helpers.migration.V287_FixInvalidArchiveState import org.thoughtcrime.securesms.database.helpers.migration.V288_CopyStickerDataHashStartToEnd +import org.thoughtcrime.securesms.database.helpers.migration.V289_AddQuoteTargetContentTypeColumn import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSqliteDatabase /** @@ -291,48 +292,49 @@ object SignalDatabaseMigrations { 285 to V285_AddEpochToCallLinksTable, 286 to V286_FixRemoteKeyEncoding, 287 to V287_FixInvalidArchiveState, - 288 to V288_CopyStickerDataHashStartToEnd + 288 to V288_CopyStickerDataHashStartToEnd, + 289 to V289_AddQuoteTargetContentTypeColumn ) - const val DATABASE_VERSION = 288 + const val DATABASE_VERSION = 289 @JvmStatic fun migrate(context: Application, db: SignalSqliteDatabase, oldVersion: Int, newVersion: Int) { val initialForeignKeyState = db.areForeignKeyConstraintsEnabled() - for (migrationData in migrations) { + val eligibleMigrations = migrations.filter { (version, _) -> version > oldVersion && version <= newVersion } + + for (migrationData in eligibleMigrations) { val (version, migration) = migrationData - if (oldVersion < version) { - Log.i(TAG, "Running migration for version $version: ${migration.javaClass.simpleName}. Foreign keys: ${migration.enableForeignKeys}") - val startTime = System.currentTimeMillis() + Log.i(TAG, "Running migration for version $version: ${migration.javaClass.simpleName}. Foreign keys: ${migration.enableForeignKeys}") + val startTime = System.currentTimeMillis() - var ftsException: SQLiteException? = null + var ftsException: SQLiteException? = null - db.setForeignKeyConstraintsEnabled(migration.enableForeignKeys) - db.beginTransaction() - try { - migration.migrate(context, db, oldVersion, newVersion) - db.version = version - db.setTransactionSuccessful() - } catch (e: SQLiteException) { - if (e.message?.contains("invalid fts5 file format") == true || e.message?.contains("vtable constructor failed") == true) { - ftsException = e - } else { - throw e - } - } finally { - db.endTransaction() + db.setForeignKeyConstraintsEnabled(migration.enableForeignKeys) + db.beginTransaction() + try { + migration.migrate(context, db, oldVersion, newVersion) + db.version = version + db.setTransactionSuccessful() + } catch (e: SQLiteException) { + if (e.message?.contains("invalid fts5 file format") == true || e.message?.contains("vtable constructor failed") == true) { + ftsException = e + } else { + throw e } - - if (ftsException != null) { - Log.w(TAG, "Encountered FTS format issue! Attempting to repair.", ftsException) - SignalDatabase.messageSearch.fullyResetTables(db) - throw ftsException - } - - Log.i(TAG, "Successfully completed migration for version $version in ${System.currentTimeMillis() - startTime} ms") + } finally { + db.endTransaction() } + + if (ftsException != null) { + Log.w(TAG, "Encountered FTS format issue! Attempting to repair.", ftsException) + SignalDatabase.messageSearch.fullyResetTables(db) + throw ftsException + } + + Log.i(TAG, "Successfully completed migration for version $version in ${System.currentTimeMillis() - startTime} ms") } db.setForeignKeyConstraintsEnabled(initialForeignKeyState) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V289_AddQuoteTargetContentTypeColumn.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V289_AddQuoteTargetContentTypeColumn.kt new file mode 100644 index 0000000000..30abdbe225 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V289_AddQuoteTargetContentTypeColumn.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.database.helpers.migration + +import android.app.Application +import org.thoughtcrime.securesms.database.SQLiteDatabase + +/** + * Adds the quote_target_content_type column to attachments and migrates existing quote attachments + * to populate this field with their current content_type. + */ +@Suppress("ClassName") +object V289_AddQuoteTargetContentTypeColumn : SignalDatabaseMigration { + + override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + db.execSQL("ALTER TABLE attachment ADD COLUMN quote_target_content_type TEXT DEFAULT NULL;") + db.execSQL("UPDATE attachment SET quote_target_content_type = content_type WHERE quote != 0;") + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java index 192697f976..604942ca28 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -374,7 +374,7 @@ public abstract class PushSendJob extends SendJob { Attachment attachment = localQuoteAttachment.get(); SignalServiceAttachment quoteAttachmentPointer = getAttachmentPointerFor(localQuoteAttachment.get()); - quoteAttachments.add(new SignalServiceDataMessage.Quote.QuotedAttachment(attachment.videoGif ? MediaUtil.IMAGE_GIF : attachment.contentType, + quoteAttachments.add(new SignalServiceDataMessage.Quote.QuotedAttachment(attachment.quoteTargetContentType != null ? attachment.quoteTargetContentType : MediaUtil.IMAGE_JPEG, attachment.fileName, quoteAttachmentPointer)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/QuoteThumbnailBackfillJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/QuoteThumbnailBackfillJob.kt index bc5218f6b4..5671c39bce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/QuoteThumbnailBackfillJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/QuoteThumbnailBackfillJob.kt @@ -83,7 +83,7 @@ class QuoteThumbnailBackfillJob private constructor(parameters: Parameters) : Jo val thumbnail = SignalDatabase.attachments.generateQuoteThumbnail(DecryptableUri(attachment.uri), attachment.contentType, quiet = true) if (thumbnail != null) { - SignalDatabase.attachments.migrationFinalizeQuoteWithData(attachment.dataFile, thumbnail) + SignalDatabase.attachments.migrationFinalizeQuoteWithData(attachment.dataFile, thumbnail, attachment.contentType) } else { Log.w(TAG, "Failed to generate thumbnail for attachment: ${attachment.id}. Clearing data.") SignalDatabase.attachments.finalizeQuoteWithNoData(attachment.dataFile) diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java index 017a0f270b..48c7bacf78 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java @@ -488,6 +488,7 @@ public class LinkPreviewRepository { null, null, null, + null, null); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt index cad5104f90..f52474df8f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt @@ -1110,7 +1110,7 @@ object DataMessageProcessor { .firstOrNull { it.hasData } if (quotedMessage.isViewOnce) { - thumbnailAttachment = TombstoneAttachment(MediaUtil.VIEW_ONCE, true) + thumbnailAttachment = TombstoneAttachment.forQuote() } else if (thumbnailAttachment == null) { thumbnailAttachment = quotedMessage .linkPreviews diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt index 2e624ee540..a1f08ffbb4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/SyncMessageProcessor.kt @@ -829,7 +829,7 @@ object SyncMessageProcessor { val giftBadge: GiftBadge? = if (dataMessage.giftBadge?.receiptCredentialPresentation != null) GiftBadge.Builder().redemptionToken(dataMessage.giftBadge!!.receiptCredentialPresentation!!).build() else null val viewOnce: Boolean = dataMessage.isViewOnce == true val bodyRanges: BodyRangeList? = dataMessage.bodyRanges.toBodyRangeList() - val syncAttachments: List = listOfNotNull(sticker) + if (viewOnce) listOf(TombstoneAttachment(MediaUtil.VIEW_ONCE, false)) else dataMessage.attachments.toPointersWithinLimit() + val syncAttachments: List = listOfNotNull(sticker) + if (viewOnce) listOf(TombstoneAttachment.forNonQuote(MediaUtil.VIEW_ONCE)) else dataMessage.attachments.toPointersWithinLimit() val mediaMessage = OutgoingMessage( recipient = recipient, diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AudioSlide.java b/app/src/main/java/org/thoughtcrime/securesms/mms/AudioSlide.java index 7261aea644..354a09e9d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AudioSlide.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/AudioSlide.java @@ -53,6 +53,7 @@ public class AudioSlide extends Slide { null, null, null, + null, null)); } @@ -61,7 +62,7 @@ public class AudioSlide extends Slide { } public AudioSlide(Uri uri, long dataSize, String contentType, boolean voiceNote) { - super(new UriAttachment(uri, contentType, AttachmentTable.TRANSFER_PROGRESS_STARTED, dataSize, 0, 0, null, null, voiceNote, false, false, false, null, null, null, null, null)); + super(new UriAttachment(uri, contentType, AttachmentTable.TRANSFER_PROGRESS_STARTED, dataSize, 0, 0, null, null, voiceNote, false, false, false, null, null, null, null, null, null)); } public AudioSlide(Attachment attachment) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.java b/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.java index 50453eede9..608dd7ad84 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/Slide.java @@ -92,6 +92,11 @@ public abstract class Slide { return attachment.fastPreflightId; } + @Nullable + public String getQuoteTargetContentType() { + return attachment.quoteTargetContentType; + } + public long getFileSize() { return attachment.size; } @@ -222,6 +227,7 @@ public abstract class Slide { borderless, gif, quote, + null, caption, stickerLocator, blurHash, diff --git a/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java b/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java index 8bceeb35e0..1a4af8c7d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java @@ -562,7 +562,7 @@ public class MessageSender { { Set finalUploadJobIds = new HashSet<>(uploadJobIds); - if (quoteAttachmentId != null) { + if (quoteAttachmentId != null && SignalDatabase.attachments().hasData(quoteAttachmentId)) { Job uploadJob = new AttachmentUploadJob(quoteAttachmentId); AppDependencies.getJobManager().add(uploadJob); finalUploadJobIds.add(uploadJob.getId()); diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/helpers/migration/V288_AddQuoteTargetContentTypeColumnTest.kt b/app/src/test/java/org/thoughtcrime/securesms/database/helpers/migration/V288_AddQuoteTargetContentTypeColumnTest.kt new file mode 100644 index 0000000000..a716730d80 --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/database/helpers/migration/V288_AddQuoteTargetContentTypeColumnTest.kt @@ -0,0 +1,176 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.database.helpers.migration + +import android.app.Application +import androidx.test.core.app.ApplicationProvider +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.isNull +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.signal.core.util.SqlUtil +import org.signal.core.util.insertInto +import org.signal.core.util.select +import org.thoughtcrime.securesms.database.AttachmentTable +import org.thoughtcrime.securesms.testutil.SignalDatabaseMigrationRule + +@Suppress("ClassName") +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE, application = Application::class) +class V288_AddQuoteTargetContentTypeColumnTest { + + @get:Rule val signalDatabaseRule = SignalDatabaseMigrationRule(287) + + private val db get() = signalDatabaseRule.database + + @Test + fun migrate_addsQuoteTargetContentTypeColumn() { + V289_AddQuoteTargetContentTypeColumn.migrate(ApplicationProvider.getApplicationContext(), db, 287, 288) + + assertThat(SqlUtil.columnExists(db, AttachmentTable.TABLE_NAME, AttachmentTable.QUOTE_TARGET_CONTENT_TYPE)).isEqualTo(true) + } + + @Test + fun migrate_whenQuoteAttachmentExists_populatesQuoteTargetContentType() { + val quoteAttachmentId = insertAttachment( + contentType = "video/mp4", + quote = 1 + ) + + V289_AddQuoteTargetContentTypeColumn.migrate(ApplicationProvider.getApplicationContext(), db, 287, 288) + + val cursor = db + .select(AttachmentTable.CONTENT_TYPE, AttachmentTable.QUOTE_TARGET_CONTENT_TYPE) + .from(AttachmentTable.TABLE_NAME) + .where("${AttachmentTable.ID} = ?", quoteAttachmentId.toString()) + .run() + + cursor.use { + assertThat(it.moveToFirst()).isEqualTo(true) + assertThat(it.getString(it.getColumnIndexOrThrow(AttachmentTable.CONTENT_TYPE))).isEqualTo("video/mp4") + assertThat(it.getString(it.getColumnIndexOrThrow(AttachmentTable.QUOTE_TARGET_CONTENT_TYPE))).isEqualTo("video/mp4") + } + } + + @Test + fun migrate_whenNonQuoteAttachmentExists_doesNotPopulateQuoteTargetContentType() { + val nonQuoteAttachmentId = insertAttachment( + contentType = "image/jpeg", + quote = 0 + ) + + V289_AddQuoteTargetContentTypeColumn.migrate(ApplicationProvider.getApplicationContext(), db, 287, 288) + + val cursor = db + .select(AttachmentTable.CONTENT_TYPE, AttachmentTable.QUOTE_TARGET_CONTENT_TYPE) + .from(AttachmentTable.TABLE_NAME) + .where("${AttachmentTable.ID} = ?", nonQuoteAttachmentId.toString()) + .run() + + cursor.use { + assertThat(it.moveToFirst()).isEqualTo(true) + assertThat(it.getString(it.getColumnIndexOrThrow(AttachmentTable.CONTENT_TYPE))).isEqualTo("image/jpeg") + assertThat(it.getString(it.getColumnIndexOrThrow(AttachmentTable.QUOTE_TARGET_CONTENT_TYPE))).isNull() + } + } + + @Test + fun migrate_whenMixedQuoteAndNonQuoteAttachments_onlyPopulatesQuoteAttachments() { + val quoteVideoId = insertAttachment( + contentType = "video/mp4", + quote = 1 + ) + val quoteImageId = insertAttachment( + contentType = "image/png", + quote = 1 + ) + val nonQuoteImageId = insertAttachment( + contentType = "image/jpeg", + quote = 0 + ) + val nonQuoteVideoId = insertAttachment( + contentType = "video/webm", + quote = 0 + ) + + V289_AddQuoteTargetContentTypeColumn.migrate(ApplicationProvider.getApplicationContext(), db, 287, 288) + + assertQuoteTargetContentType(quoteVideoId, expectedContentType = "video/mp4", expectedQuoteTargetContentType = "video/mp4") + assertQuoteTargetContentType(quoteImageId, expectedContentType = "image/png", expectedQuoteTargetContentType = "image/png") + assertQuoteTargetContentType(nonQuoteImageId, expectedContentType = "image/jpeg", expectedQuoteTargetContentType = null) + assertQuoteTargetContentType(nonQuoteVideoId, expectedContentType = "video/webm", expectedQuoteTargetContentType = null) + } + + @Test + fun migrate_whenQuoteAttachmentWithNullContentType_setsQuoteTargetContentTypeToNull() { + val quoteAttachmentId = insertAttachment( + contentType = null, + quote = 1 + ) + + V289_AddQuoteTargetContentTypeColumn.migrate(ApplicationProvider.getApplicationContext(), db, 287, 288) + + val cursor = db + .select(AttachmentTable.CONTENT_TYPE, AttachmentTable.QUOTE_TARGET_CONTENT_TYPE) + .from(AttachmentTable.TABLE_NAME) + .where("${AttachmentTable.ID} = ?", quoteAttachmentId.toString()) + .run() + + cursor.use { + assertThat(it.moveToFirst()).isEqualTo(true) + assertThat(it.getString(it.getColumnIndexOrThrow(AttachmentTable.CONTENT_TYPE))).isNull() + assertThat(it.getString(it.getColumnIndexOrThrow(AttachmentTable.QUOTE_TARGET_CONTENT_TYPE))).isNull() + } + } + + private fun insertAttachment(contentType: String?, quote: Int): Long { + return db + .insertInto(AttachmentTable.TABLE_NAME) + .values( + AttachmentTable.MESSAGE_ID to 1L, + AttachmentTable.CONTENT_TYPE to contentType, + AttachmentTable.DATA_FILE to "/fake/path/attachment.jpg", + AttachmentTable.DATA_RANDOM to "/fake/path/attachment.jpg".toByteArray(), + AttachmentTable.TRANSFER_STATE to AttachmentTable.TRANSFER_PROGRESS_DONE, + AttachmentTable.QUOTE to quote, + AttachmentTable.DATA_SIZE to 1024L, + AttachmentTable.WIDTH to 100, + AttachmentTable.HEIGHT to 100 + ) + .run() + } + + private fun assertQuoteTargetContentType(attachmentId: Long, expectedContentType: String?, expectedQuoteTargetContentType: String?) { + val cursor = db + .select(AttachmentTable.CONTENT_TYPE, AttachmentTable.QUOTE_TARGET_CONTENT_TYPE) + .from(AttachmentTable.TABLE_NAME) + .where("${AttachmentTable.ID} = ?", attachmentId.toString()) + .run() + + cursor.use { + assertThat(it.moveToFirst()).isEqualTo(true) + + val actualContentType = it.getString(it.getColumnIndexOrThrow(AttachmentTable.CONTENT_TYPE)) + val actualQuoteTargetContentType = it.getString(it.getColumnIndexOrThrow(AttachmentTable.QUOTE_TARGET_CONTENT_TYPE)) + + if (expectedContentType == null) { + assertThat(actualContentType).isNull() + } else { + assertThat(actualContentType).isEqualTo(expectedContentType) + } + + if (expectedQuoteTargetContentType == null) { + assertThat(actualQuoteTargetContentType).isNull() + } else { + assertThat(actualQuoteTargetContentType).isEqualTo(expectedQuoteTargetContentType) + } + } + } +} diff --git a/app/src/test/java/org/thoughtcrime/securesms/sms/UploadDependencyGraphTest.kt b/app/src/test/java/org/thoughtcrime/securesms/sms/UploadDependencyGraphTest.kt index 3b03b3589d..0958513a1f 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/sms/UploadDependencyGraphTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/sms/UploadDependencyGraphTest.kt @@ -255,7 +255,8 @@ class UploadDependencyGraphTest { archiveCdn = 0, thumbnailRestoreState = AttachmentTable.ThumbnailRestoreState.NONE, archiveTransferState = AttachmentTable.ArchiveTransferState.NONE, - uuid = null + uuid = null, + quoteTargetContentType = null ) } diff --git a/app/src/test/java/org/thoughtcrime/securesms/testutil/UriAttachmentBuilder.kt b/app/src/test/java/org/thoughtcrime/securesms/testutil/UriAttachmentBuilder.kt index 39b7c68306..ba2fef8081 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/testutil/UriAttachmentBuilder.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/testutil/UriAttachmentBuilder.kt @@ -19,6 +19,7 @@ object UriAttachmentBuilder { borderless: Boolean = false, videoGif: Boolean = false, quote: Boolean = false, + quoteTargetContentType: String? = null, caption: String? = null, stickerLocator: StickerLocator? = null, blurHash: BlurHash? = null, @@ -35,6 +36,7 @@ object UriAttachmentBuilder { borderless, videoGif, quote, + quoteTargetContentType, caption, stickerLocator, blurHash, diff --git a/app/src/testShared/org/thoughtcrime/securesms/database/FakeMessageRecords.kt b/app/src/testShared/org/thoughtcrime/securesms/database/FakeMessageRecords.kt index 71ffdd3451..eb95ffa1df 100644 --- a/app/src/testShared/org/thoughtcrime/securesms/database/FakeMessageRecords.kt +++ b/app/src/testShared/org/thoughtcrime/securesms/database/FakeMessageRecords.kt @@ -100,7 +100,8 @@ object FakeMessageRecords { archiveCdn = archiveCdn, thumbnailRestoreState = thumbnailRestoreState, archiveTransferState = archiveTransferState, - uuid = null + uuid = null, + quoteTargetContentType = null ) }