mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 02:10:44 +01:00
Migrate quotes to have a separate quoteTargetContentType.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -40,6 +40,7 @@ class LocalStickerAttachment : Attachment {
|
||||
height = StickerSlide.HEIGHT,
|
||||
incrementalMacChunkSize = 0,
|
||||
quote = false,
|
||||
quoteTargetContentType = null,
|
||||
uploadTimestamp = 0,
|
||||
caption = null,
|
||||
stickerLocator = stickerLocator,
|
||||
|
||||
@@ -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<SignalServiceAttachment>,
|
||||
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<Attachment> {
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -30,6 +30,7 @@ class WallpaperAttachment() : Attachment(
|
||||
height = 0,
|
||||
incrementalMacChunkSize = 0,
|
||||
quote = false,
|
||||
quoteTargetContentType = null,
|
||||
uploadTimestamp = 0,
|
||||
caption = null,
|
||||
stickerLocator = null,
|
||||
|
||||
@@ -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<DatabaseAttachment>.toRemoteQuoteAttachments(): List<Quote.QuotedAttachment> {
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<Attachment> {
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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(""));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ class InternalConversationSettingsFragment : ComposeFragment(), InternalConversa
|
||||
borderless = false,
|
||||
videoGif = false,
|
||||
quote = false,
|
||||
quoteTargetContentType = null,
|
||||
caption = null,
|
||||
stickerLocator = null,
|
||||
blurHash = null,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -88,7 +88,8 @@ class V2ConversationItemMediaViewHolder<Model : MappingModel<Model>>(
|
||||
quote.isOriginalMissing,
|
||||
quote.attachment,
|
||||
if (conversationMessage.messageRecord.isStoryReaction()) conversationMessage.messageRecord.body else null,
|
||||
quote.quoteType
|
||||
quote.quoteType,
|
||||
false
|
||||
)
|
||||
|
||||
quoteView.setMessageType(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;")
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -488,6 +488,7 @@ public class LinkPreviewRepository {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Attachment> = listOfNotNull(sticker) + if (viewOnce) listOf<Attachment>(TombstoneAttachment(MediaUtil.VIEW_ONCE, false)) else dataMessage.attachments.toPointersWithinLimit()
|
||||
val syncAttachments: List<Attachment> = listOfNotNull(sticker) + if (viewOnce) listOf<Attachment>(TombstoneAttachment.forNonQuote(MediaUtil.VIEW_ONCE)) else dataMessage.attachments.toPointersWithinLimit()
|
||||
|
||||
val mediaMessage = OutgoingMessage(
|
||||
recipient = recipient,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -562,7 +562,7 @@ public class MessageSender {
|
||||
{
|
||||
Set<String> 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());
|
||||
|
||||
Reference in New Issue
Block a user