Fix crash from external share.

This commit is contained in:
Alex Hart
2023-04-25 16:20:28 -03:00
parent 40663eb52f
commit 6c57c2ac2a
15 changed files with 550 additions and 225 deletions

View File

@@ -1,9 +1,12 @@
package org.thoughtcrime.securesms.conversation
import org.thoughtcrime.securesms.recipients.Recipient
/**
* Represents metadata about a conversation.
*/
data class ConversationData(
val threadRecipient: Recipient,
val threadId: Long,
val lastSeen: Long,
val lastSeenPosition: Int,

View File

@@ -13,6 +13,12 @@ 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.conversation.v2.data.AttachmentHelper;
import org.thoughtcrime.securesms.conversation.v2.data.CallHelper;
import org.thoughtcrime.securesms.conversation.v2.data.MentionHelper;
import org.thoughtcrime.securesms.conversation.v2.data.PaymentHelper;
import org.thoughtcrime.securesms.conversation.v2.data.QuotedHelper;
import org.thoughtcrime.securesms.conversation.v2.data.ReactionHelper;
import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
@@ -24,25 +30,15 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.database.model.UpdateDescription;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.payments.Payment;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Core data source for loading an individual conversation.
@@ -59,14 +55,22 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
/** Used once for the initial fetch, then cleared. */
private int baseSize;
private Recipient threadRecipient;
private final Recipient threadRecipient;
public ConversationDataSource(@NonNull Context context, long threadId, @NonNull MessageRequestData messageRequestData, boolean showUniversalExpireTimerUpdate, int baseSize) {
public ConversationDataSource(
@NonNull Context context,
long threadId,
@NonNull MessageRequestData messageRequestData,
boolean showUniversalExpireTimerUpdate,
int baseSize,
@NonNull Recipient threadRecipient
) {
this.context = context;
this.threadId = threadId;
this.messageRequestData = messageRequestData;
this.showUniversalExpireTimerUpdate = showUniversalExpireTimerUpdate;
this.baseSize = baseSize;
this.threadRecipient = threadRecipient;
}
@Override
@@ -168,8 +172,6 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
records = callHelper.buildUpdatedModels(records);
stopwatch.split("call-models");
ensureThreadRecipient();
for (ServiceId serviceId : referencedIds) {
Recipient.resolved(RecipientId.from(serviceId));
}
@@ -234,7 +236,6 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
stopwatch.split("calls");
ensureThreadRecipient();
return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(ApplicationDependencies.getApplication(),
record,
record.getDisplayBody(ApplicationDependencies.getApplication()),
@@ -253,197 +254,4 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
public @NonNull MessageId getKey(@NonNull ConversationMessage conversationMessage) {
return new MessageId(conversationMessage.getMessageRecord().getId());
}
private static class MentionHelper {
private Collection<Long> messageIds = new LinkedList<>();
private Map<Long, List<Mention>> messageIdToMentions = new HashMap<>();
void add(MessageRecord record) {
if (record.isMms()) {
messageIds.add(record.getId());
}
}
void fetchMentions(Context context) {
messageIdToMentions = SignalDatabase.mentions().getMentionsForMessages(messageIds);
}
@Nullable List<Mention> getMentions(long id) {
return messageIdToMentions.get(id);
}
}
private void ensureThreadRecipient() {
if (threadRecipient == null) {
threadRecipient = Objects.requireNonNull(SignalDatabase.threads().getRecipientForThreadId(threadId));
}
}
private static class QuotedHelper {
private Collection<MessageRecord> records = new LinkedList<>();
private Set<Long> hasBeenQuotedIds = new HashSet<>();
void add(MessageRecord record) {
records.add(record);
}
void fetchQuotedState() {
hasBeenQuotedIds = SignalDatabase.messages().isQuoted(records);
}
boolean isQuoted(long id) {
return hasBeenQuotedIds.contains(id);
}
}
public static class AttachmentHelper {
private Collection<Long> messageIds = new LinkedList<>();
private Map<Long, List<DatabaseAttachment>> messageIdToAttachments = new HashMap<>();
public void add(MessageRecord record) {
if (record.isMms()) {
messageIds.add(record.getId());
}
}
public void addAll(List<MessageRecord> records) {
for (MessageRecord record : records) {
add(record);
}
}
public void fetchAttachments() {
messageIdToAttachments = SignalDatabase.attachments().getAttachmentsForMessages(messageIds);
}
public @NonNull List<MessageRecord> buildUpdatedModels(@NonNull Context context, @NonNull List<MessageRecord> records) {
return records.stream()
.map(record -> {
if (record instanceof MediaMmsMessageRecord) {
List<DatabaseAttachment> attachments = messageIdToAttachments.get(record.getId());
if (Util.hasItems(attachments)) {
return ((MediaMmsMessageRecord) record).withAttachments(context, attachments);
}
}
return record;
})
.collect(Collectors.toList());
}
}
public static class ReactionHelper {
private Collection<MessageId> messageIds = new LinkedList<>();
private Map<MessageId, List<ReactionRecord>> messageIdToReactions = new HashMap<>();
public void add(MessageRecord record) {
messageIds.add(new MessageId(record.getId()));
}
public void addAll(List<MessageRecord> records) {
for (MessageRecord record : records) {
add(record);
}
}
public void fetchReactions() {
messageIdToReactions = SignalDatabase.reactions().getReactionsForMessages(messageIds);
}
public @NonNull List<MessageRecord> buildUpdatedModels(@NonNull List<MessageRecord> records) {
return records.stream()
.map(record -> {
MessageId messageId = new MessageId(record.getId());
List<ReactionRecord> reactions = messageIdToReactions.get(messageId);
return recordWithReactions(record, reactions);
})
.collect(Collectors.toList());
}
private static MessageRecord recordWithReactions(@NonNull MessageRecord record, List<ReactionRecord> reactions) {
if (Util.hasItems(reactions)) {
if (record instanceof MediaMmsMessageRecord) {
return ((MediaMmsMessageRecord) record).withReactions(reactions);
} else {
throw new IllegalStateException("We have reactions for an unsupported record type: " + record.getClass().getName());
}
} else {
return record;
}
}
}
private static class PaymentHelper {
private final Map<UUID, Long> paymentMessages = new HashMap<>();
private final Map<Long, Payment> messageIdToPayment = new HashMap<>();
public void add(MessageRecord messageRecord) {
if (messageRecord.isMms() && messageRecord.isPaymentNotification()) {
UUID paymentUuid = UuidUtil.parseOrNull(messageRecord.getBody());
if (paymentUuid != null) {
paymentMessages.put(paymentUuid, messageRecord.getId());
}
}
}
public void fetchPayments() {
List<Payment> payments = SignalDatabase.payments().getPayments(paymentMessages.keySet());
for (Payment payment : payments) {
if (payment != null) {
messageIdToPayment.put(paymentMessages.get(payment.getUuid()), payment);
}
}
}
@NonNull List<MessageRecord> buildUpdatedModels(@NonNull List<MessageRecord> records) {
return records.stream()
.map(record -> {
if (record instanceof MediaMmsMessageRecord) {
Payment payment = messageIdToPayment.get(record.getId());
if (payment != null) {
return ((MediaMmsMessageRecord) record).withPayment(payment);
}
}
return record;
})
.collect(Collectors.toList());
}
}
private static class CallHelper {
private final Collection<Long> messageIds = new LinkedList<>();
private Map<Long, CallTable.Call> messageIdToCall = Collections.emptyMap();
public void add(MessageRecord messageRecord) {
if (messageRecord.isCallLog() && !messageRecord.isGroupCall()) {
messageIds.add(messageRecord.getId());
}
}
public void fetchCalls() {
if (!messageIds.isEmpty()) {
messageIdToCall = SignalDatabase.calls().getCalls(messageIds);
}
}
@NonNull List<MessageRecord> buildUpdatedModels(@NonNull List<MessageRecord> records) {
return records.stream()
.map(record -> {
if (record.isCallLog() && record instanceof MediaMmsMessageRecord) {
CallTable.Call call = messageIdToCall.get(record.getId());
if (call != null) {
return ((MediaMmsMessageRecord) record).withCall(call);
}
}
return record;
})
.collect(Collectors.toList());
}
}
}

View File

@@ -65,8 +65,8 @@ public class ConversationRepository {
@WorkerThread
public @NonNull ConversationData getConversationData(long threadId, @NonNull Recipient conversationRecipient, int jumpToPosition) {
ThreadTable.ConversationMetadata metadata = SignalDatabase.threads().getConversationMetadata(threadId);
int threadSize = SignalDatabase.messages().getMessageCountForThread(threadId);
ThreadTable.ConversationMetadata metadata = SignalDatabase.threads().getConversationMetadata(threadId);
int threadSize = SignalDatabase.messages().getMessageCountForThread(threadId);
long lastSeen = metadata.getLastSeen();
int lastSeenPosition = 0;
long lastScrolled = metadata.getLastScrolled();
@@ -118,7 +118,7 @@ public class ConversationRepository {
showUniversalExpireTimerUpdate = true;
}
return new ConversationData(threadId, lastSeen, lastSeenPosition, lastScrolledPosition, jumpToPosition, threadSize, messageRequestData, showUniversalExpireTimerUpdate);
return new ConversationData(conversationRecipient, threadId, lastSeen, lastSeenPosition, lastScrolledPosition, jumpToPosition, threadSize, messageRequestData, showUniversalExpireTimerUpdate);
}
public void markGiftBadgeRevealed(long messageId) {

View File

@@ -186,7 +186,13 @@ public class ConversationViewModel extends ViewModel {
ApplicationDependencies.getDatabaseObserver().registerConversationObserver(data.getThreadId(), conversationObserver);
ApplicationDependencies.getDatabaseObserver().registerMessageInsertObserver(data.getThreadId(), messageInsertObserver);
ConversationDataSource dataSource = new ConversationDataSource(context, data.getThreadId(), messageRequestData, data.showUniversalExpireTimerMessage(), data.getThreadSize());
ConversationDataSource dataSource = new ConversationDataSource(context,
data.getThreadId(),
messageRequestData,
data.showUniversalExpireTimerMessage(),
data.getThreadSize(),
data.getThreadRecipient());
PagingConfig config = new PagingConfig.Builder().setPageSize(25)
.setBufferPages(2)
.setStartIndex(Math.max(startPosition, 0))

View File

@@ -4,6 +4,7 @@ import android.content.Context
import androidx.annotation.WorkerThread
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.conversation.v2.data.AttachmentHelper
import org.thoughtcrime.securesms.database.DatabaseObserver
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.MessageRecord
@@ -35,7 +36,7 @@ class ScheduledMessagesRepository {
var scheduledMessages: List<MessageRecord> = SignalDatabase.messages.getScheduledMessagesInThread(threadId)
val threadRecipient: Recipient = requireNotNull(SignalDatabase.threads.getRecipientForThreadId(threadId))
val attachmentHelper = ConversationDataSource.AttachmentHelper()
val attachmentHelper = AttachmentHelper()
attachmentHelper.addAll(scheduledMessages)

View File

@@ -4,9 +4,10 @@ import android.app.Application
import androidx.annotation.WorkerThread
import io.reactivex.rxjava3.core.Observable
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.conversation.ConversationDataSource
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory
import org.thoughtcrime.securesms.conversation.v2.data.AttachmentHelper
import org.thoughtcrime.securesms.conversation.v2.data.ReactionHelper
import org.thoughtcrime.securesms.database.DatabaseObserver
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
@@ -56,8 +57,8 @@ class MessageQuotesRepository {
var replyRecords: List<MessageRecord> = SignalDatabase.messages.getAllMessagesThatQuote(rootMessageId)
val reactionHelper = ConversationDataSource.ReactionHelper()
val attachmentHelper = ConversationDataSource.AttachmentHelper()
val reactionHelper = ReactionHelper()
val attachmentHelper = AttachmentHelper()
val threadRecipient = requireNotNull(SignalDatabase.threads.getRecipientForThreadId(originalRecord.threadId))
reactionHelper.addAll(replyRecords)
@@ -84,7 +85,7 @@ class MessageQuotesRepository {
originalRecord = SignalDatabase.payments.updateMessageWithPayment(originalRecord)
}
originalRecord = ConversationDataSource.ReactionHelper()
originalRecord = ReactionHelper()
.apply {
add(originalRecord)
fetchReactions()
@@ -92,7 +93,7 @@ class MessageQuotesRepository {
.buildUpdatedModels(listOf(originalRecord))
.get(0)
originalRecord = ConversationDataSource.AttachmentHelper()
originalRecord = AttachmentHelper()
.apply {
add(originalRecord)
fetchAttachments()

View File

@@ -2,8 +2,8 @@ package org.thoughtcrime.securesms.conversation.ui.edit
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.conversation.ConversationDataSource
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.v2.data.AttachmentHelper
import org.thoughtcrime.securesms.database.DatabaseObserver
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
@@ -36,7 +36,7 @@ object EditMessageHistoryRepository {
.getMessageEditHistory(messageId)
.toList()
val attachmentHelper = ConversationDataSource.AttachmentHelper()
val attachmentHelper = AttachmentHelper()
.apply {
addAll(records)
fetchAttachments()

View File

@@ -9,12 +9,11 @@ import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.paging.PagedData
import org.signal.paging.PagingConfig
import org.thoughtcrime.securesms.conversation.ConversationDataSource
import org.thoughtcrime.securesms.conversation.colors.GroupAuthorNameColorHelper
import org.thoughtcrime.securesms.conversation.colors.NameColor
import org.thoughtcrime.securesms.conversation.v2.data.ConversationDataSource
import org.thoughtcrime.securesms.database.RxDatabaseObserver
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.threads
import org.thoughtcrime.securesms.database.model.Quote
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
@@ -54,7 +53,7 @@ class ConversationRepository(context: Context) {
fun getConversationThreadState(threadId: Long, requestedStartPosition: Int): Single<ConversationThreadState> {
return Single.fromCallable {
SignalLocalMetrics.ConversationOpen.onMetadataLoadStarted()
val recipient = threads.getRecipientForThreadId(threadId)!!
val recipient = SignalDatabase.threads.getRecipientForThreadId(threadId)!!
val metadata = oldConversationRepository.getConversationData(threadId, recipient, requestedStartPosition)
val messageRequestData = metadata.messageRequestData
val dataSource = ConversationDataSource(
@@ -98,7 +97,7 @@ class ConversationRepository(context: Context) {
}
fun setLastVisibleMessageTimestamp(threadId: Long, lastVisibleMessageTimestamp: Long) {
SignalExecutors.BOUNDED.submit { threads.setLastScrolled(threadId, lastVisibleMessageTimestamp) }
SignalExecutors.BOUNDED.submit { SignalDatabase.threads.setLastScrolled(threadId, lastVisibleMessageTimestamp) }
}
fun markGiftBadgeRevealed(messageId: Long) {
@@ -130,7 +129,7 @@ class ConversationRepository(context: Context) {
}
private fun getUnreadCount(threadId: Long): Int {
val threadRecord = threads.getThreadRecord(threadId)
val threadRecord = SignalDatabase.threads.getThreadRecord(threadId)
return threadRecord?.unreadCount ?: 0
}

View File

@@ -0,0 +1,56 @@
package org.thoughtcrime.securesms.conversation.v2.data;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.util.Util;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class AttachmentHelper {
private Collection<Long> messageIds = new LinkedList<>();
private Map<Long, List<DatabaseAttachment>> messageIdToAttachments = new HashMap<>();
public void add(MessageRecord record) {
if (record.isMms()) {
messageIds.add(record.getId());
}
}
public void addAll(List<MessageRecord> records) {
for (MessageRecord record : records) {
add(record);
}
}
public void fetchAttachments() {
messageIdToAttachments = SignalDatabase.attachments().getAttachmentsForMessages(messageIds);
}
public @NonNull List<MessageRecord> buildUpdatedModels(@NonNull Context context, @NonNull List<MessageRecord> records) {
return records.stream()
.map(record -> {
if (record instanceof MediaMmsMessageRecord) {
List<DatabaseAttachment> attachments = messageIdToAttachments.get(record.getId());
if (Util.hasItems(attachments)) {
return ((MediaMmsMessageRecord) record).withAttachments(context, attachments);
}
}
return record;
})
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,46 @@
package org.thoughtcrime.securesms.conversation.v2.data;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CallHelper {
private final Collection<Long> messageIds = new LinkedList<>();
private Map<Long, CallTable.Call> messageIdToCall = Collections.emptyMap();
public void add(MessageRecord messageRecord) {
if (messageRecord.isCallLog() && !messageRecord.isGroupCall()) {
messageIds.add(messageRecord.getId());
}
}
public void fetchCalls() {
if (!messageIds.isEmpty()) {
messageIdToCall = SignalDatabase.calls().getCalls(messageIds);
}
}
public @NonNull List<MessageRecord> buildUpdatedModels(@NonNull List<MessageRecord> records) {
return records.stream()
.map(record -> {
if (record.isCallLog() && record instanceof MediaMmsMessageRecord) {
CallTable.Call call = messageIdToCall.get(record.getId());
if (call != null) {
return ((MediaMmsMessageRecord) record).withCall(call);
}
}
return record;
})
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,231 @@
package org.thoughtcrime.securesms.conversation.v2.data
import android.content.Context
import org.signal.core.util.Stopwatch
import org.signal.core.util.logging.Log
import org.signal.core.util.toInt
import org.signal.paging.PagedDataSource
import org.thoughtcrime.securesms.conversation.ConversationData
import org.thoughtcrime.securesms.conversation.ConversationDataSource
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord.NoGroupsInCommon
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord.RemovedContactHidden
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord.UniversalExpireTimerUpdate
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ServiceId
/**
* ConversationDataSource for V2. Assumes that ThreadId is never -1L.
*/
class ConversationDataSource(
private val context: Context,
private val threadId: Long,
private val messageRequestData: ConversationData.MessageRequestData,
private val showUniversalExpireTimerUpdate: Boolean,
private var baseSize: Int
) : PagedDataSource<MessageId, ConversationMessage> {
init {
check(threadId > 0)
}
companion object {
private val TAG = Log.tag(ConversationDataSource::class.java)
}
private val threadRecipient: Recipient by lazy {
SignalDatabase.threads.getRecipientForThreadId(threadId)!!
}
override fun size(): Int {
val startTime = System.currentTimeMillis()
val size: Int = getSizeInternal() +
messageRequestData.includeWarningUpdateMessage().toInt() +
messageRequestData.isHidden.toInt() +
showUniversalExpireTimerUpdate.toInt()
Log.d(TAG, "[size(), thread $threadId] ${System.currentTimeMillis() - startTime} ms")
return size
}
private fun getSizeInternal(): Int {
synchronized(this) {
if (baseSize != -1) {
val size = baseSize
baseSize = -1
return size
}
}
return SignalDatabase.messages.getMessageCountForThread(threadId)
}
override fun load(start: Int, length: Int, cancellationSignal: PagedDataSource.CancellationSignal): List<ConversationMessage> {
val stopwatch = Stopwatch("load($start, $length), thread $threadId")
var records: MutableList<MessageRecord> = ArrayList(length)
val mentionHelper = MentionHelper()
val quotedHelper = QuotedHelper()
val attachmentHelper = AttachmentHelper()
val reactionHelper = ReactionHelper()
val paymentHelper = PaymentHelper()
val callHelper = CallHelper()
val referencedIds = hashSetOf<ServiceId>()
MessageTable.mmsReaderFor(SignalDatabase.messages.getConversation(threadId, start.toLong(), length.toLong())).forEach { record ->
if (cancellationSignal.isCanceled) {
return@forEach
}
records.add(record)
mentionHelper.add(record)
quotedHelper.add(record)
reactionHelper.add(record)
attachmentHelper.add(record)
paymentHelper.add(record)
callHelper.add(record)
val updateDescription = record.getUpdateDisplayBody(context, null)
if (updateDescription != null) {
referencedIds.addAll(updateDescription.mentioned)
}
}
if (messageRequestData.includeWarningUpdateMessage() && (start + length >= size())) {
records.add(NoGroupsInCommon(threadId, messageRequestData.isGroup))
}
if (messageRequestData.isHidden && (start + length >= size())) {
records.add(RemovedContactHidden(threadId))
}
if (showUniversalExpireTimerUpdate) {
records.add(UniversalExpireTimerUpdate(threadId))
}
stopwatch.split("messages")
mentionHelper.fetchMentions(context)
stopwatch.split("mentions")
quotedHelper.fetchQuotedState()
stopwatch.split("is-quoted")
reactionHelper.fetchReactions()
stopwatch.split("reactions")
records = reactionHelper.buildUpdatedModels(records)
stopwatch.split("reaction-models")
attachmentHelper.fetchAttachments()
stopwatch.split("attachments")
records = attachmentHelper.buildUpdatedModels(context, records)
stopwatch.split("attachment-models")
paymentHelper.fetchPayments()
stopwatch.split("payments")
records = paymentHelper.buildUpdatedModels(records)
stopwatch.split("payment-models")
callHelper.fetchCalls()
stopwatch.split("calls")
records = callHelper.buildUpdatedModels(records)
stopwatch.split("call-models")
referencedIds.forEach { Recipient.resolved(RecipientId.from(it)) }
stopwatch.split("recipient-resolves")
val messages = records.map { m ->
ConversationMessageFactory.createWithUnresolvedData(
context,
m,
m.getDisplayBody(context),
mentionHelper.getMentions(m.id),
quotedHelper.isQuoted(m.id),
threadRecipient
)
}
stopwatch.split("conversion")
stopwatch.stop(TAG)
return messages
}
override fun load(messageId: MessageId): ConversationMessage? {
val stopwatch = Stopwatch("load($messageId), thread $threadId")
var record = SignalDatabase.messages.getMessageRecordOrNull(messageId.id)
if ((record as? MediaMmsMessageRecord)?.parentStoryId?.isGroupReply() == true) {
return null
}
val scheduleDate = (record as? MediaMmsMessageRecord)?.scheduledDate
if (scheduleDate != null && scheduleDate != -1L) {
return null
}
stopwatch.split("message")
try {
if (record == null) {
return null
} else {
val mentions = SignalDatabase.mentions.getMentionsForMessage(messageId.id)
stopwatch.split("mentions")
val isQuoted = SignalDatabase.messages.isQuoted(record)
stopwatch.split("is-quoted")
val reactions = SignalDatabase.reactions.getReactions(messageId)
record = ReactionHelper.recordWithReactions(record, reactions)
stopwatch.split("reactions")
val attachments = SignalDatabase.attachments.getAttachmentsForMessage(messageId.id)
if (attachments.size > 0) {
record = (record as MediaMmsMessageRecord).withAttachments(context, attachments)
}
stopwatch.split("attachments")
if (record.isPaymentNotification) {
record = SignalDatabase.payments.updateMessageWithPayment(record)
}
stopwatch.split("payments")
if (record.isCallLog && !record.isGroupCall) {
val call = SignalDatabase.calls.getCallByMessageId(record.id)
if (call != null && record is MediaMmsMessageRecord) {
record = record.withCall(call)
}
}
stopwatch.split("calls")
return ConversationMessageFactory.createWithUnresolvedData(
ApplicationDependencies.getApplication(),
record,
record.getDisplayBody(ApplicationDependencies.getApplication()),
mentions,
isQuoted,
threadRecipient
)
}
} finally {
stopwatch.stop(TAG)
}
}
override fun getKey(conversationMessage: ConversationMessage): MessageId {
return MessageId(conversationMessage.messageRecord.id)
}
}

View File

@@ -0,0 +1,35 @@
package org.thoughtcrime.securesms.conversation.v2.data;
import android.content.Context;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class MentionHelper {
private Collection<Long> messageIds = new LinkedList<>();
private Map<Long, List<Mention>> messageIdToMentions = new HashMap<>();
public void add(MessageRecord record) {
if (record.isMms()) {
messageIds.add(record.getId());
}
}
public void fetchMentions(Context context) {
messageIdToMentions = SignalDatabase.mentions().getMentionsForMessages(messageIds);
}
public @Nullable List<Mention> getMentions(long id) {
return messageIdToMentions.get(id);
}
}

View File

@@ -0,0 +1,52 @@
package org.thoughtcrime.securesms.conversation.v2.data;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.payments.Payment;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
public class PaymentHelper {
private final Map<UUID, Long> paymentMessages = new HashMap<>();
private final Map<Long, Payment> messageIdToPayment = new HashMap<>();
public void add(MessageRecord messageRecord) {
if (messageRecord.isMms() && messageRecord.isPaymentNotification()) {
UUID paymentUuid = UuidUtil.parseOrNull(messageRecord.getBody());
if (paymentUuid != null) {
paymentMessages.put(paymentUuid, messageRecord.getId());
}
}
}
public void fetchPayments() {
List<Payment> payments = SignalDatabase.payments().getPayments(paymentMessages.keySet());
for (Payment payment : payments) {
if (payment != null) {
messageIdToPayment.put(paymentMessages.get(payment.getUuid()), payment);
}
}
}
public @NonNull List<MessageRecord> buildUpdatedModels(@NonNull List<MessageRecord> records) {
return records.stream()
.map(record -> {
if (record instanceof MediaMmsMessageRecord) {
Payment payment = messageIdToPayment.get(record.getId());
if (payment != null) {
return ((MediaMmsMessageRecord) record).withPayment(payment);
}
}
return record;
})
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,27 @@
package org.thoughtcrime.securesms.conversation.v2.data;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
public class QuotedHelper {
private Collection<MessageRecord> records = new LinkedList<>();
private Set<Long> hasBeenQuotedIds = new HashSet<>();
public void add(MessageRecord record) {
records.add(record);
}
public void fetchQuotedState() {
hasBeenQuotedIds = SignalDatabase.messages().isQuoted(records);
}
public boolean isQuoted(long id) {
return hasBeenQuotedIds.contains(id);
}
}

View File

@@ -0,0 +1,60 @@
package org.thoughtcrime.securesms.conversation.v2.data;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.util.Util;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ReactionHelper {
private Collection<MessageId> messageIds = new LinkedList<>();
private Map<MessageId, List<ReactionRecord>> messageIdToReactions = new HashMap<>();
public void add(MessageRecord record) {
messageIds.add(new MessageId(record.getId()));
}
public void addAll(List<MessageRecord> records) {
for (MessageRecord record : records) {
add(record);
}
}
public void fetchReactions() {
messageIdToReactions = SignalDatabase.reactions().getReactionsForMessages(messageIds);
}
public @NonNull List<MessageRecord> buildUpdatedModels(@NonNull List<MessageRecord> records) {
return records.stream()
.map(record -> {
MessageId messageId = new MessageId(record.getId());
List<ReactionRecord> reactions = messageIdToReactions.get(messageId);
return recordWithReactions(record, reactions);
})
.collect(Collectors.toList());
}
public static @NonNull MessageRecord recordWithReactions(@NonNull MessageRecord record, List<ReactionRecord> reactions) {
if (Util.hasItems(reactions)) {
if (record instanceof MediaMmsMessageRecord) {
return ((MediaMmsMessageRecord) record).withReactions(reactions);
} else {
throw new IllegalStateException("We have reactions for an unsupported record type: " + record.getClass().getName());
}
} else {
return record;
}
}
}