mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 09:20:19 +01:00
Generate thumbnails for quote attachments.
This commit is contained in:
@@ -95,12 +95,15 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob
|
||||
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob
|
||||
import org.thoughtcrime.securesms.jobs.GenerateAudioWaveFormJob
|
||||
import org.thoughtcrime.securesms.mms.DecryptableUri
|
||||
import org.thoughtcrime.securesms.mms.MediaStream
|
||||
import org.thoughtcrime.securesms.mms.MmsException
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import org.thoughtcrime.securesms.mms.SentMediaQuality
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException
|
||||
import org.thoughtcrime.securesms.util.FileUtils
|
||||
import org.thoughtcrime.securesms.util.ImageCompressionUtil
|
||||
import org.thoughtcrime.securesms.util.JsonUtils.SaneJSONObject
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
@@ -299,6 +302,9 @@ class AttachmentTable(
|
||||
ID, DATA_FILE, DATA_SIZE, DATA_RANDOM, DATA_HASH_START, DATA_HASH_END, TRANSFORM_PROPERTIES, UPLOAD_TIMESTAMP, ARCHIVE_CDN, ARCHIVE_TRANSFER_STATE, THUMBNAIL_FILE, THUMBNAIL_RESTORE_STATE, THUMBNAIL_RANDOM
|
||||
)
|
||||
|
||||
private const val QUOTE_THUMBNAIL_DIMEN = 150
|
||||
private const val QUOTE_IMAGE_QUALITY = 80
|
||||
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun newDataFile(context: Context): File {
|
||||
@@ -1789,7 +1795,7 @@ class AttachmentTable(
|
||||
try {
|
||||
for (attachment in quoteAttachment) {
|
||||
val attachmentId = when {
|
||||
attachment.uri != null -> insertAttachmentWithData(mmsId, attachment, true)
|
||||
attachment.uri != null -> insertQuoteAttachment(mmsId, attachment)
|
||||
attachment is ArchivedAttachment -> insertArchivedAttachment(mmsId, attachment, true)
|
||||
else -> insertUndownloadedAttachment(mmsId, attachment, true)
|
||||
}
|
||||
@@ -2409,6 +2415,104 @@ class AttachmentTable(
|
||||
return attachmentId
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Throws(MmsException::class)
|
||||
private fun insertQuoteAttachment(messageId: Long, attachment: Attachment): AttachmentId {
|
||||
Log.d(TAG, "[insertQuoteAttachment] Inserting quote attachment for messageId $messageId.")
|
||||
|
||||
val thumbnail = generateQuoteThumbnail(DecryptableUri(attachment.uri!!), attachment.contentType)
|
||||
if (thumbnail != null) {
|
||||
Log.d(TAG, "[insertQuoteAttachment] Successfully generated quote thumbnail for messageId $messageId.")
|
||||
|
||||
return insertAttachmentWithData(
|
||||
messageId = messageId,
|
||||
dataStream = thumbnail.data.inputStream(),
|
||||
attachment = attachment,
|
||||
quote = true
|
||||
)
|
||||
}
|
||||
|
||||
Log.d(TAG, "[insertQuoteAttachment] Unable to generate quote thumbnail for messageId $messageId. Content type: ${attachment.contentType}")
|
||||
val attachmentId: AttachmentId = writableDatabase.withinTransaction { db ->
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MESSAGE_ID, messageId)
|
||||
put(CONTENT_TYPE, attachment.contentType)
|
||||
put(VOICE_NOTE, attachment.voiceNote.toInt())
|
||||
put(BORDERLESS, attachment.borderless.toInt())
|
||||
put(VIDEO_GIF, attachment.videoGif.toInt())
|
||||
put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE)
|
||||
put(DATA_SIZE, 0)
|
||||
put(WIDTH, attachment.width)
|
||||
put(HEIGHT, attachment.height)
|
||||
put(QUOTE, 1)
|
||||
put(BLUR_HASH, attachment.blurHash?.hash)
|
||||
put(FILE_NAME, attachment.fileName)
|
||||
|
||||
attachment.stickerLocator?.let { sticker ->
|
||||
put(STICKER_PACK_ID, sticker.packId)
|
||||
put(STICKER_PACK_KEY, sticker.packKey)
|
||||
put(STICKER_ID, sticker.stickerId)
|
||||
put(STICKER_EMOJI, sticker.emoji)
|
||||
}
|
||||
}
|
||||
|
||||
val rowId = db.insert(TABLE_NAME, null, contentValues)
|
||||
AttachmentId(rowId)
|
||||
}
|
||||
|
||||
AppDependencies.databaseObserver.notifyAttachmentUpdatedObservers()
|
||||
return attachmentId
|
||||
}
|
||||
|
||||
private fun generateQuoteThumbnail(uri: DecryptableUri, contentType: String?): ImageCompressionUtil.Result? {
|
||||
return try {
|
||||
when {
|
||||
MediaUtil.isImageType(contentType) -> {
|
||||
val hasTransparency = MediaUtil.isPngType(contentType) || MediaUtil.isWebpType(contentType)
|
||||
val outputFormat = if (hasTransparency) MediaUtil.IMAGE_WEBP else MediaUtil.IMAGE_JPEG
|
||||
|
||||
ImageCompressionUtil.compress(
|
||||
context,
|
||||
contentType,
|
||||
outputFormat,
|
||||
uri,
|
||||
QUOTE_THUMBNAIL_DIMEN,
|
||||
QUOTE_IMAGE_QUALITY
|
||||
)
|
||||
}
|
||||
MediaUtil.isVideoType(contentType) -> {
|
||||
val videoThumbnail = MediaUtil.getVideoThumbnail(context, uri.uri)
|
||||
if (videoThumbnail != null) {
|
||||
ImageCompressionUtil.compress(
|
||||
context,
|
||||
MediaUtil.IMAGE_JPEG,
|
||||
MediaUtil.IMAGE_JPEG,
|
||||
uri,
|
||||
QUOTE_THUMBNAIL_DIMEN,
|
||||
QUOTE_IMAGE_QUALITY
|
||||
)
|
||||
} else {
|
||||
Log.w(TAG, "[generateQuoteThumbnail] Failed to extract video thumbnail")
|
||||
null
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Log.w(TAG, "[generateQuoteThumbnail] Unsupported content type for thumbnail generation: $contentType")
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: BitmapDecodingException) {
|
||||
Log.w(TAG, "[generateQuoteThumbnail] Failed to decode image for thumbnail", e)
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "[generateQuoteThumbnail] Failed to generate thumbnail", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attachments need records in the database even if they haven't been downloaded yet. That allows us to store the info we need to download it, what message
|
||||
* it's associated with, etc. We treat this case separately from attachments with data (see [insertAttachmentWithData]) because it's much simpler,
|
||||
|
||||
@@ -2548,11 +2548,11 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val quoteText = cursor.requireString(QUOTE_BODY)
|
||||
val quoteType = cursor.requireInt(QUOTE_TYPE)
|
||||
val quoteMissing = cursor.requireBoolean(QUOTE_MISSING)
|
||||
val quoteAttachments: List<Attachment> = associatedAttachments.filter { it.quote }.toList()
|
||||
val quoteAttachment: Attachment? = associatedAttachments.filter { it.quote }.firstOrNull()
|
||||
val quoteMentions: List<Mention> = parseQuoteMentions(cursor)
|
||||
val quoteBodyRanges: BodyRangeList? = parseQuoteBodyRanges(cursor)
|
||||
val quote: QuoteModel? = if (quoteId != QUOTE_NOT_PRESENT_ID && quoteAuthor > 0 && (!TextUtils.isEmpty(quoteText) || quoteAttachments.isNotEmpty())) {
|
||||
QuoteModel(quoteId, RecipientId.from(quoteAuthor), quoteText ?: "", quoteMissing, quoteAttachments, quoteMentions, QuoteModel.Type.fromCode(quoteType), quoteBodyRanges)
|
||||
val quote: QuoteModel? = if (quoteId != QUOTE_NOT_PRESENT_ID && quoteAuthor > 0 && (!TextUtils.isEmpty(quoteText) || quoteAttachment != null)) {
|
||||
QuoteModel(quoteId, RecipientId.from(quoteAuthor), quoteText ?: "", quoteMissing, quoteAttachment, quoteMentions, QuoteModel.Type.fromCode(quoteType), quoteBodyRanges)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -2776,7 +2776,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
contentValues.put(QUOTE_BODY_RANGES, quoteBodyRanges.build().encode())
|
||||
}
|
||||
|
||||
quoteAttachments += retrieved.quote.attachments
|
||||
retrieved.quote.attachment?.let { quoteAttachments += it }
|
||||
} else {
|
||||
contentValues.put(QUOTE_ID, 0)
|
||||
contentValues.put(QUOTE_AUTHOR, 0)
|
||||
@@ -2869,7 +2869,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
messageId = messageId,
|
||||
threadId = threadId,
|
||||
threadWasNewlyCreated = threadIdResult.newlyCreated,
|
||||
insertedAttachments = insertedAttachments
|
||||
insertedAttachments = insertedAttachments,
|
||||
quoteAttachmentId = quoteAttachments.firstOrNull()?.let { insertedAttachments?.get(it) }
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -2982,7 +2983,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
threadId: Long,
|
||||
forceSms: Boolean = false,
|
||||
insertListener: InsertListener? = null
|
||||
): Long {
|
||||
): InsertResult {
|
||||
return insertMessageOutbox(
|
||||
message = message,
|
||||
threadId = threadId,
|
||||
@@ -2999,7 +3000,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
forceSms: Boolean,
|
||||
defaultReceiptStatus: Int,
|
||||
insertListener: InsertListener?
|
||||
): Long {
|
||||
): InsertResult {
|
||||
var type = MessageTypes.BASE_SENDING_TYPE
|
||||
var hasSpecialType = false
|
||||
|
||||
@@ -3218,7 +3219,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
|
||||
if (editedMessage == null) {
|
||||
quoteAttachments += message.outgoingQuote.attachments
|
||||
message.outgoingQuote.attachment?.let { quoteAttachments += it }
|
||||
}
|
||||
} else {
|
||||
contentValues.put(QUOTE_ID, 0)
|
||||
@@ -3320,7 +3321,13 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
|
||||
TrimThreadJob.enqueueAsync(threadId)
|
||||
|
||||
return messageId
|
||||
return InsertResult(
|
||||
messageId = messageId,
|
||||
threadId = threadId,
|
||||
threadWasNewlyCreated = false,
|
||||
insertedAttachments = insertedAttachments,
|
||||
quoteAttachmentId = quoteAttachments.firstOrNull()?.let { insertedAttachments?.get(it) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun hasAudioAttachment(attachments: List<Attachment>): Boolean {
|
||||
@@ -5255,7 +5262,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
timetamp = this.requireLong(DATE_SENT)
|
||||
),
|
||||
expirationInfo = null,
|
||||
storyType = StoryType.fromCode(this.requireInt(STORY_TYPE)),
|
||||
storyType = fromCode(this.requireInt(STORY_TYPE)),
|
||||
dateReceived = this.requireLong(DATE_RECEIVED)
|
||||
)
|
||||
}
|
||||
@@ -5406,7 +5413,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val messageId: Long,
|
||||
val threadId: Long,
|
||||
val threadWasNewlyCreated: Boolean,
|
||||
val insertedAttachments: Map<Attachment, AttachmentId>? = null
|
||||
val insertedAttachments: Map<Attachment, AttachmentId>? = null,
|
||||
val quoteAttachmentId: AttachmentId? = null
|
||||
)
|
||||
|
||||
data class MessageReceiptUpdate(
|
||||
|
||||
Reference in New Issue
Block a user