diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java index 8b810d48c..d0abcc025 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java @@ -9,14 +9,17 @@ import com.annimon.stream.Stream; import org.signal.core.util.logging.Log; import org.signal.paging.PagedDataSource; +import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.conversation.ConversationData.MessageRequestData; import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord; +import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.util.Stopwatch; +import org.thoughtcrime.securesms.util.Util; import java.util.ArrayList; import java.util.Collection; @@ -24,6 +27,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * Core data source for loading an individual conversation. @@ -58,16 +62,18 @@ class ConversationDataSource implements PagedDataSource { @Override public @NonNull List load(int start, int length, @NonNull CancellationSignal cancellationSignal) { - Stopwatch stopwatch = new Stopwatch("load(" + start + ", " + length + "), thread " + threadId); - MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context); - List records = new ArrayList<>(length); - MentionHelper mentionHelper = new MentionHelper(); + Stopwatch stopwatch = new Stopwatch("load(" + start + ", " + length + "), thread " + threadId); + MmsSmsDatabase db = DatabaseFactory.getMmsSmsDatabase(context); + List records = new ArrayList<>(length); + MentionHelper mentionHelper = new MentionHelper(); + AttachmentHelper attachmentHelper = new AttachmentHelper(); try (MmsSmsDatabase.Reader reader = MmsSmsDatabase.readerFor(db.getConversation(threadId, start, length))) { MessageRecord record; while ((record = reader.getNext()) != null && !cancellationSignal.isCanceled()) { records.add(record); mentionHelper.add(record); + attachmentHelper.add(record); } } @@ -85,6 +91,12 @@ class ConversationDataSource implements PagedDataSource { stopwatch.split("mentions"); + attachmentHelper.fetchAttachments(context); + + stopwatch.split("attachments"); + + records = attachmentHelper.buildUpdatedModels(context, records); + List messages = Stream.of(records) .map(m -> ConversationMessageFactory.createWithUnresolvedData(context, m, mentionHelper.getMentions(m.getId()))) .toList(); @@ -114,4 +126,37 @@ class ConversationDataSource implements PagedDataSource { return messageIdToMentions.get(id); } } + + private static class AttachmentHelper { + + private Collection messageIds = new LinkedList<>(); + private Map> messageIdToAttachments = new HashMap<>(); + + void add(MessageRecord record) { + if (record.isMms()) { + messageIds.add(record.getId()); + } + } + + void fetchAttachments(Context context) { + messageIdToAttachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessages(messageIds); + } + + @NonNull List buildUpdatedModels(@NonNull Context context, @NonNull List records) { + return records.stream() + .map(record -> { + if (record instanceof MediaMmsMessageRecord) { + List attachments = messageIdToAttachments.get(record.getId()); + + if (Util.hasItems(attachments)) { + return ((MediaMmsMessageRecord) record).withAttachments(context, attachments); + } + } + + return record; + }) + .collect(Collectors.toList()); + } + } + } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java index 5ccb911c9..7141b88ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -60,6 +60,7 @@ import org.thoughtcrime.securesms.util.FileUtils; import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.SetUtil; +import org.thoughtcrime.securesms.util.SqlUtil; import org.thoughtcrime.securesms.util.StorageUtil; import org.thoughtcrime.securesms.video.EncryptedMediaDataSource; import org.whispersystems.libsignal.util.guava.Optional; @@ -236,7 +237,7 @@ public class AttachmentDatabase extends Database { cursor = database.query(TABLE_NAME, PROJECTION, PART_ID_WHERE, attachmentId.toStrings(), null, null, null); if (cursor != null && cursor.moveToFirst()) { - List list = getAttachment(cursor); + List list = getAttachments(cursor); if (list != null && list.size() > 0) { return list.get(0); @@ -260,7 +261,7 @@ public class AttachmentDatabase extends Database { null, null, UNIQUE_ID + " ASC, " + ROW_ID + " ASC"); while (cursor != null && cursor.moveToNext()) { - results.addAll(getAttachment(cursor)); + results.addAll(getAttachments(cursor)); } return results; @@ -270,6 +271,33 @@ public class AttachmentDatabase extends Database { } } + public @NonNull Map> getAttachmentsForMessages(@NonNull Collection mmsIds) { + if (mmsIds.isEmpty()) { + return Collections.emptyMap(); + } + + SQLiteDatabase database = databaseHelper.getReadableDatabase(); + SqlUtil.Query query = SqlUtil.buildCollectionQuery(MMS_ID, mmsIds); + + Map> output = new HashMap<>(); + + try (Cursor cursor = database.query(TABLE_NAME, PROJECTION, query.getWhere(), query.getWhereArgs(), null, null, UNIQUE_ID + " ASC, " + ROW_ID + " ASC")) { + while (cursor.moveToNext()) { + DatabaseAttachment attachment = getAttachment(cursor); + List attachments = output.get(attachment.getMmsId()); + + if (attachments == null) { + attachments = new LinkedList<>(); + output.put(attachment.getMmsId(), attachments); + } + + attachments.add(attachment); + } + } + + return output; + } + public boolean hasAttachment(@NonNull AttachmentId id) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); @@ -304,7 +332,7 @@ public class AttachmentDatabase extends Database { try { cursor = database.query(TABLE_NAME, PROJECTION, TRANSFER_STATE + " = ?", new String[] {String.valueOf(TRANSFER_PROGRESS_STARTED)}, null, null, null); while (cursor != null && cursor.moveToNext()) { - attachments.addAll(getAttachment(cursor)); + attachments.addAll(getAttachments(cursor)); } } finally { if (cursor != null) cursor.close(); @@ -1116,7 +1144,7 @@ public class AttachmentDatabase extends Database { return Pair.create(selector, selection); } - public List getAttachment(@NonNull Cursor cursor) { + public List getAttachments(@NonNull Cursor cursor) { try { if (cursor.getColumnIndex(AttachmentDatabase.ATTACHMENT_JSON_ALIAS) != -1) { if (cursor.isNull(cursor.getColumnIndexOrThrow(ATTACHMENT_JSON_ALIAS))) { @@ -1168,46 +1196,50 @@ public class AttachmentDatabase extends Database { return result; } else { - String contentType = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)); - return Collections.singletonList(new DatabaseAttachment(new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(ROW_ID)), - cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))), - cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)), - !cursor.isNull(cursor.getColumnIndexOrThrow(DATA)), - MediaUtil.isImageType(contentType) || MediaUtil.isVideoType(contentType), - contentType, - cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)), - cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)), - cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME)), - cursor.getInt(cursor.getColumnIndexOrThrow(CDN_NUMBER)), - cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)), - cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)), - cursor.getString(cursor.getColumnIndexOrThrow(NAME)), - cursor.getBlob(cursor.getColumnIndexOrThrow(DIGEST)), - cursor.getString(cursor.getColumnIndexOrThrow(FAST_PREFLIGHT_ID)), - cursor.getInt(cursor.getColumnIndexOrThrow(VOICE_NOTE)) == 1, - cursor.getInt(cursor.getColumnIndexOrThrow(BORDERLESS)) == 1, - cursor.getInt(cursor.getColumnIndexOrThrow(VIDEO_GIF)) == 1, - cursor.getInt(cursor.getColumnIndexOrThrow(WIDTH)), - cursor.getInt(cursor.getColumnIndexOrThrow(HEIGHT)), - cursor.getInt(cursor.getColumnIndexOrThrow(QUOTE)) == 1, - cursor.getString(cursor.getColumnIndexOrThrow(CAPTION)), - cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID)) >= 0 - ? new StickerLocator(CursorUtil.requireString(cursor, STICKER_PACK_ID), - CursorUtil.requireString(cursor, STICKER_PACK_KEY), - CursorUtil.requireInt(cursor, STICKER_ID), - CursorUtil.requireString(cursor, STICKER_EMOJI)) - : null, - MediaUtil.isAudioType(contentType) ? null : BlurHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(VISUAL_HASH))), - MediaUtil.isAudioType(contentType) ? AudioHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(VISUAL_HASH))) : null, - TransformProperties.parse(cursor.getString(cursor.getColumnIndexOrThrow(TRANSFORM_PROPERTIES))), - cursor.getInt(cursor.getColumnIndexOrThrow(DISPLAY_ORDER)), - cursor.getLong(cursor.getColumnIndexOrThrow(UPLOAD_TIMESTAMP)))); + return Collections.singletonList(getAttachment(cursor)); } } catch (JSONException e) { throw new AssertionError(e); } } + private @NonNull DatabaseAttachment getAttachment(@NonNull Cursor cursor) { + String contentType = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)); + return new DatabaseAttachment(new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(ROW_ID)), + cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))), + cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)), + !cursor.isNull(cursor.getColumnIndexOrThrow(DATA)), + MediaUtil.isImageType(contentType) || MediaUtil.isVideoType(contentType), + contentType, + cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)), + cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)), + cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME)), + cursor.getInt(cursor.getColumnIndexOrThrow(CDN_NUMBER)), + cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)), + cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)), + cursor.getString(cursor.getColumnIndexOrThrow(NAME)), + cursor.getBlob(cursor.getColumnIndexOrThrow(DIGEST)), + cursor.getString(cursor.getColumnIndexOrThrow(FAST_PREFLIGHT_ID)), + cursor.getInt(cursor.getColumnIndexOrThrow(VOICE_NOTE)) == 1, + cursor.getInt(cursor.getColumnIndexOrThrow(BORDERLESS)) == 1, + cursor.getInt(cursor.getColumnIndexOrThrow(VIDEO_GIF)) == 1, + cursor.getInt(cursor.getColumnIndexOrThrow(WIDTH)), + cursor.getInt(cursor.getColumnIndexOrThrow(HEIGHT)), + cursor.getInt(cursor.getColumnIndexOrThrow(QUOTE)) == 1, + cursor.getString(cursor.getColumnIndexOrThrow(CAPTION)), + cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID)) >= 0 + ? new StickerLocator(CursorUtil.requireString(cursor, STICKER_PACK_ID), + CursorUtil.requireString(cursor, STICKER_PACK_KEY), + CursorUtil.requireInt(cursor, STICKER_ID), + CursorUtil.requireString(cursor, STICKER_EMOJI)) + : null, + MediaUtil.isAudioType(contentType) ? null : BlurHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(VISUAL_HASH))), + MediaUtil.isAudioType(contentType) ? AudioHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(VISUAL_HASH))) : null, + TransformProperties.parse(cursor.getString(cursor.getColumnIndexOrThrow(TRANSFORM_PROPERTIES))), + cursor.getInt(cursor.getColumnIndexOrThrow(DISPLAY_ORDER)), + cursor.getLong(cursor.getColumnIndexOrThrow(UPLOAD_TIMESTAMP))); + } + private AttachmentId insertAttachment(long mmsId, Attachment attachment, boolean quote) throws MmsException { @@ -1309,7 +1341,7 @@ public class AttachmentDatabase extends Database { try (Cursor cursor = databaseHelper.getWritableDatabase().query(TABLE_NAME, null, selection, args, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { - return getAttachment(cursor).get(0); + return getAttachments(cursor).get(0); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java index bf3386809..9c2379732 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -205,7 +205,7 @@ public class MediaDatabase extends Database { public static MediaRecord from(@NonNull Context context, @NonNull Cursor cursor) { AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context); - List attachments = attachmentDatabase.getAttachment(cursor); + List attachments = attachmentDatabase.getAttachments(cursor); RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.RECIPIENT_ID))); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID)); boolean outgoing = MessageDatabase.Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX))); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 5393495ff..445167029 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -1252,6 +1252,7 @@ public class MmsDatabase extends MessageDatabase { Avatar updatedAvatar = new Avatar(contact.getAvatar().getAttachmentId(), attachment, contact.getAvatar().isProfile()); + contacts.add(new Contact(contact, updatedAvatar)); } else { contacts.add(contact); @@ -1289,6 +1290,8 @@ public class MmsDatabase extends MessageDatabase { DatabaseAttachment attachment = attachmentIdMap.get(preview.getAttachmentId()); if (attachment != null) { previews.add(new LinkPreview(preview.getUrl(), preview.getTitle(), preview.getDescription(), preview.getDate(), attachment)); + } else { + previews.add(preview); } } else { previews.add(preview); @@ -2084,12 +2087,12 @@ public class MmsDatabase extends MessageDatabase { Recipient recipient = Recipient.live(RecipientId.from(recipientId)).get(); List mismatches = getMismatchedIdentities(mismatchDocument); List networkFailures = getFailures(networkDocument); - List attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachment(cursor); + List attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachments(cursor); List contacts = getSharedContacts(cursor, attachments); Set contactAttachments = Stream.of(contacts).map(Contact::getAvatarAttachment).withoutNulls().collect(Collectors.toSet()); List previews = getLinkPreviews(cursor, attachments); Set previewAttachments = Stream.of(previews).filter(lp -> lp.getThumbnail().isPresent()).map(lp -> lp.getThumbnail().get()).collect(Collectors.toSet()); - SlideDeck slideDeck = getSlideDeck(Stream.of(attachments).filterNot(contactAttachments::contains).filterNot(previewAttachments::contains).toList()); + SlideDeck slideDeck = buildSlideDeck(context, Stream.of(attachments).filterNot(contactAttachments::contains).filterNot(previewAttachments::contains).toList()); Quote quote = getQuote(cursor); return new MediaMmsMessageRecord(id, recipient, recipient, @@ -2124,7 +2127,7 @@ public class MmsDatabase extends MessageDatabase { return new LinkedList<>(); } - private SlideDeck getSlideDeck(@NonNull List attachments) { + public static SlideDeck buildSlideDeck(@NonNull Context context, @NonNull List attachments) { List messageAttachments = Stream.of(attachments) .filterNot(Attachment::isQuote) .sorted(new DatabaseAttachment.DisplayOrderComparator()) @@ -2138,7 +2141,7 @@ public class MmsDatabase extends MessageDatabase { CharSequence quoteText = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_BODY)); boolean quoteMissing = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_MISSING)) == 1; List quoteMentions = parseQuoteMentions(context, cursor); - List attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachment(cursor); + List attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachments(cursor); List quoteAttachments = Stream.of(attachments).filter(Attachment::isQuote).toList(); SlideDeck quoteDeck = new SlideDeck(context, quoteAttachments); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index f06404d6c..790e32090 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -183,11 +183,13 @@ public class MmsSmsDatabase extends Database { public Cursor getConversation(long threadId, long offset, long limit) { - String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; - String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; - String limitStr = limit > 0 || offset > 0 ? offset + ", " + limit : null; + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; + String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; + String limitStr = limit > 0 || offset > 0 ? offset + ", " + limit : null; + String query = buildQuery(PROJECTION, selection, order, limitStr, false); - Cursor cursor = queryTables(PROJECTION, selection, order, limitStr); + Cursor cursor = db.rawQuery(query, null); setNotifyConversationListeners(cursor, threadId); return cursor; @@ -585,43 +587,46 @@ public class MmsSmsDatabase extends Database { .collect(Collectors.toList()); } - private Cursor queryTables(String[] projection, String selection, String order, String limit) { + private static @NonNull String buildQuery(String[] projection, String selection, String order, String limit, boolean includeAttachments) { + String attachmentJsonJoin; + if (includeAttachments) { + attachmentJsonJoin = "json_group_array(json_object(" + "'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " + + "'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " + + "'" + AttachmentDatabase.MMS_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + "," + + "'" + AttachmentDatabase.SIZE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.SIZE + ", " + + "'" + AttachmentDatabase.FILE_NAME + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FILE_NAME + ", " + + "'" + AttachmentDatabase.DATA + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", " + + "'" + AttachmentDatabase.CONTENT_TYPE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_TYPE + ", " + + "'" + AttachmentDatabase.CDN_NUMBER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CDN_NUMBER + ", " + + "'" + AttachmentDatabase.CONTENT_LOCATION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_LOCATION + ", " + + "'" + AttachmentDatabase.FAST_PREFLIGHT_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FAST_PREFLIGHT_ID + ", " + + "'" + AttachmentDatabase.VOICE_NOTE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VOICE_NOTE + ", " + + "'" + AttachmentDatabase.BORDERLESS + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.BORDERLESS + ", " + + "'" + AttachmentDatabase.VIDEO_GIF + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VIDEO_GIF + ", " + + "'" + AttachmentDatabase.WIDTH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.WIDTH + ", " + + "'" + AttachmentDatabase.HEIGHT + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.HEIGHT + ", " + + "'" + AttachmentDatabase.QUOTE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.QUOTE + ", " + + "'" + AttachmentDatabase.CONTENT_DISPOSITION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_DISPOSITION + ", " + + "'" + AttachmentDatabase.NAME + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.NAME + ", " + + "'" + AttachmentDatabase.TRANSFER_STATE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFER_STATE + ", " + + "'" + AttachmentDatabase.CAPTION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CAPTION + ", " + + "'" + AttachmentDatabase.STICKER_PACK_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_ID + ", " + + "'" + AttachmentDatabase.STICKER_PACK_KEY + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_KEY + ", " + + "'" + AttachmentDatabase.STICKER_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_ID + ", " + + "'" + AttachmentDatabase.STICKER_EMOJI + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_EMOJI + ", " + + "'" + AttachmentDatabase.VISUAL_HASH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VISUAL_HASH + ", " + + "'" + AttachmentDatabase.TRANSFORM_PROPERTIES + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFORM_PROPERTIES + ", " + + "'" + AttachmentDatabase.DISPLAY_ORDER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + ", " + + "'" + AttachmentDatabase.UPLOAD_TIMESTAMP + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UPLOAD_TIMESTAMP + "))"; + } else { + attachmentJsonJoin = "NULL"; + } + String[] mmsProjection = {MmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT, MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED, MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " AS " + MmsSmsColumns.ID, - "'MMS::' || " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID - + " || '::' || " + MmsDatabase.DATE_SENT - + " AS " + MmsSmsColumns.UNIQUE_ROW_ID, - "json_group_array(json_object(" + - "'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " + - "'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " + - "'" + AttachmentDatabase.MMS_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + "," + - "'" + AttachmentDatabase.SIZE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.SIZE + ", " + - "'" + AttachmentDatabase.FILE_NAME + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FILE_NAME + ", " + - "'" + AttachmentDatabase.DATA + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", " + - "'" + AttachmentDatabase.CONTENT_TYPE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_TYPE + ", " + - "'" + AttachmentDatabase.CDN_NUMBER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CDN_NUMBER + ", " + - "'" + AttachmentDatabase.CONTENT_LOCATION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_LOCATION + ", " + - "'" + AttachmentDatabase.FAST_PREFLIGHT_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FAST_PREFLIGHT_ID + ", " + - "'" + AttachmentDatabase.VOICE_NOTE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VOICE_NOTE + ", " + - "'" + AttachmentDatabase.BORDERLESS + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.BORDERLESS + ", " + - "'" + AttachmentDatabase.VIDEO_GIF + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VIDEO_GIF + ", " + - "'" + AttachmentDatabase.WIDTH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.WIDTH + ", " + - "'" + AttachmentDatabase.HEIGHT + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.HEIGHT + ", " + - "'" + AttachmentDatabase.QUOTE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.QUOTE + ", " + - "'" + AttachmentDatabase.CONTENT_DISPOSITION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_DISPOSITION + ", " + - "'" + AttachmentDatabase.NAME + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.NAME + ", " + - "'" + AttachmentDatabase.TRANSFER_STATE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFER_STATE + ", " + - "'" + AttachmentDatabase.CAPTION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CAPTION + ", " + - "'" + AttachmentDatabase.STICKER_PACK_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_ID + ", " + - "'" + AttachmentDatabase.STICKER_PACK_KEY + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_PACK_KEY + ", " + - "'" + AttachmentDatabase.STICKER_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_ID + ", " + - "'" + AttachmentDatabase.STICKER_EMOJI + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.STICKER_EMOJI + ", " + - "'" + AttachmentDatabase.VISUAL_HASH + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VISUAL_HASH + ", " + - "'" + AttachmentDatabase.TRANSFORM_PROPERTIES + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFORM_PROPERTIES + ", " + - "'" + AttachmentDatabase.DISPLAY_ORDER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DISPLAY_ORDER + ", " + - "'" + AttachmentDatabase.UPLOAD_TIMESTAMP + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UPLOAD_TIMESTAMP + - ")) AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS, + "'MMS::' || " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " || '::' || " + MmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.UNIQUE_ROW_ID, + attachmentJsonJoin + " AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID, SmsDatabase.TYPE, SmsDatabase.RECIPIENT_ID, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE, MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, @@ -653,10 +658,7 @@ public class MmsSmsDatabase extends Database { String[] smsProjection = {SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT, SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED, - MmsSmsColumns.ID, - "'SMS::' || " + MmsSmsColumns.ID - + " || '::' || " + SmsDatabase.DATE_SENT - + " AS " + MmsSmsColumns.UNIQUE_ROW_ID, + MmsSmsColumns.ID, "'SMS::' || " + MmsSmsColumns.ID + " || '::' || " + SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.UNIQUE_ROW_ID, "NULL AS " + AttachmentDatabase.ATTACHMENT_JSON_ALIAS, SmsDatabase.BODY, MmsSmsColumns.READ, MmsSmsColumns.THREAD_ID, SmsDatabase.TYPE, SmsDatabase.RECIPIENT_ID, SmsDatabase.ADDRESS_DEVICE_ID, SmsDatabase.SUBJECT, MmsDatabase.MESSAGE_TYPE, @@ -690,14 +692,19 @@ public class MmsSmsDatabase extends Database { SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); - mmsQueryBuilder.setDistinct(true); - smsQueryBuilder.setDistinct(true); + if (includeAttachments) { + mmsQueryBuilder.setDistinct(true); + smsQueryBuilder.setDistinct(true); + } smsQueryBuilder.setTables(SmsDatabase.TABLE_NAME); - mmsQueryBuilder.setTables(MmsDatabase.TABLE_NAME + " LEFT OUTER JOIN " + - AttachmentDatabase.TABLE_NAME + - " ON " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID); + if (includeAttachments) { + mmsQueryBuilder.setTables(MmsDatabase.TABLE_NAME + " LEFT OUTER JOIN " + AttachmentDatabase.TABLE_NAME + + " ON " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID); + } else { + mmsQueryBuilder.setTables(MmsDatabase.TABLE_NAME); + } Set mmsColumnsPresent = new HashSet<>(); mmsColumnsPresent.add(MmsSmsColumns.ID); @@ -770,22 +777,24 @@ public class MmsSmsDatabase extends Database { smsColumnsPresent.add(MmsDatabase.REMOTE_DELETED); smsColumnsPresent.add(MmsSmsColumns.NOTIFIED_TIMESTAMP); - @SuppressWarnings("deprecation") - String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(TRANSPORT, mmsProjection, mmsColumnsPresent, 4, MMS_TRANSPORT, selection, null, MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID, null); - @SuppressWarnings("deprecation") + String mmsGroupBy = includeAttachments ? MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID : null; + + String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(TRANSPORT, mmsProjection, mmsColumnsPresent, 4, MMS_TRANSPORT, selection, null, mmsGroupBy, null); String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(TRANSPORT, smsProjection, smsColumnsPresent, 4, SMS_TRANSPORT, selection, null, null, null); SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder(); - String unionQuery = unionQueryBuilder.buildUnionQuery(new String[] {smsSubQuery, mmsSubQuery}, order, limit); + String unionQuery = unionQueryBuilder.buildUnionQuery(new String[] { smsSubQuery, mmsSubQuery }, order, limit); SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder(); outerQueryBuilder.setTables("(" + unionQuery + ")"); - @SuppressWarnings("deprecation") - String query = outerQueryBuilder.buildQuery(projection, null, null, null, null, null, null); + return outerQueryBuilder.buildQuery(projection, null, null, null, null, null, null); + } - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - return db.rawQuery(query, null); + private Cursor queryTables(String[] projection, String selection, String order, String limit) { + String query = buildQuery(projection, selection, order, limit, true); + + return databaseHelper.getReadableDatabase().rawQuery(query, null); } public static Reader readerFor(@NonNull Cursor cursor) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index fa7bba8d7..d1ce333ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -24,6 +24,9 @@ import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.attachments.Attachment; +import org.thoughtcrime.securesms.attachments.AttachmentId; +import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase.Status; @@ -32,8 +35,14 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.recipients.Recipient; +import org.whispersystems.libsignal.util.guava.Optional; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; /** * Represents the message record model for MMS messages that contain @@ -87,10 +96,6 @@ public class MediaMmsMessageRecord extends MmsMessageRecord { this.mentionsSelf = mentionsSelf; } - public int getPartCount() { - return partCount; - } - @Override public boolean hasSelfMention() { return mentionsSelf; @@ -115,4 +120,62 @@ public class MediaMmsMessageRecord extends MmsMessageRecord { return super.getDisplayBody(context); } + + public int getPartCount() { + return partCount; + } + + public MediaMmsMessageRecord withAttachments(@NonNull Context context, @NonNull List attachments) { + Map attachmentIdMap = new HashMap<>(); + for (DatabaseAttachment attachment : attachments) { + attachmentIdMap.put(attachment.getAttachmentId(), attachment); + } + + List contacts = updateContacts(getSharedContacts(), attachmentIdMap); + Set contactAttachments = contacts.stream().map(Contact::getAvatarAttachment).filter(Objects::nonNull).collect(Collectors.toSet()); + List linkPreviews = updateLinkPreviews(getLinkPreviews(), attachmentIdMap); + Set linkPreviewAttachments = linkPreviews.stream().map(LinkPreview::getThumbnail).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toSet()); + + List slideAttachments = attachments.stream().filter(a -> !contactAttachments.contains(a)).filter(a -> !linkPreviewAttachments.contains(a)).collect(Collectors.toList()); + + SlideDeck slideDeck = MmsDatabase.Reader.buildSlideDeck(context, slideAttachments); + return new MediaMmsMessageRecord(getId(), getRecipient(), getIndividualRecipient(), getRecipientDeviceId(), getDateSent(), getDateReceived(), getServerTimestamp(), getDeliveryReceiptCount(), getThreadId(), getBody(), slideDeck, + getPartCount(), getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(), + getReadReceiptCount(), getQuote(), contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf, + getNotifiedTimestamp(), getViewedReceiptCount()); + } + + private static @NonNull List updateContacts(@NonNull List contacts, @NonNull Map attachmentIdMap) { + return contacts.stream() + .map(contact -> { + if (contact.getAvatar() != null) { + DatabaseAttachment attachment = attachmentIdMap.get(contact.getAvatar().getAttachmentId()); + Contact.Avatar updatedAvatar = new Contact.Avatar(contact.getAvatar().getAttachmentId(), + attachment, + contact.getAvatar().isProfile()); + + return new Contact(contact, updatedAvatar); + } else { + return contact; + } + }) + .collect(Collectors.toList()); + } + + private static @NonNull List updateLinkPreviews(@NonNull List linkPreviews, @NonNull Map attachmentIdMap) { + return linkPreviews.stream() + .map(preview -> { + if (preview.getAttachmentId() != null) { + DatabaseAttachment attachment = attachmentIdMap.get(preview.getAttachmentId()); + if (attachment != null) { + return new LinkPreview(preview.getUrl(), preview.getTitle(), preview.getDescription(), preview.getDate(), attachment); + } else { + return preview; + } + } else { + return preview; + } + }) + .collect(Collectors.toList()); + } }