diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageContentProcessor__handleTextMessageTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageContentProcessor__handleTextMessageTest.kt index 33b12031d0..5994e4ad95 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageContentProcessor__handleTextMessageTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/messages/MessageContentProcessor__handleTextMessageTest.kt @@ -24,7 +24,7 @@ class MessageContentProcessor__handleTextMessageTest : MessageContentProcessorTe // THEN val record = SignalDatabase.messages.getMessageRecord(1) - val threadSize = SignalDatabase.mmsSms.getConversationCount(record.threadId) + val threadSize = SignalDatabase.messages.getMessageCountForThread(record.threadId) assertEquals(1, threadSize) assertTrue(record.isSecure) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaItemFactory.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaItemFactory.java index eeeacedb6e..c4554c29ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaItemFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNoteMediaItemFactory.java @@ -80,9 +80,9 @@ class VoiceNoteMediaItemFactory { @Nullable static MediaItem buildMediaItem(@NonNull Context context, @NonNull MessageRecord messageRecord) { - int startingPosition = SignalDatabase.mmsSms() - .getMessagePositionInConversation(messageRecord.getThreadId(), - messageRecord.getDateReceived()); + int startingPosition = SignalDatabase.messages() + .getMessagePositionInConversation(messageRecord.getThreadId(), + messageRecord.getDateReceived()); Recipient threadRecipient = Objects.requireNonNull(SignalDatabase.threads() .getRecipientForThreadId(messageRecord.getThreadId())); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackPreparer.java b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackPreparer.java index f64ce2f261..e5e38f29f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackPreparer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/voice/VoiceNotePlaybackPreparer.java @@ -267,8 +267,7 @@ final class VoiceNotePlaybackPreparer implements MediaSessionConnector.Playbac @WorkerThread private @NonNull List loadMediaItemsForConsecutivePlayback(long messageId) { try { - List recordsAfter = SignalDatabase.mmsSms() - .getMessagesAfterVoiceNoteInclusive(messageId, LIMIT); + List recordsAfter = SignalDatabase.messages().getMessagesAfterVoiceNoteInclusive(messageId, LIMIT); return buildFilteredMessageRecordList(recordsAfter).stream() .map(record -> VoiceNoteMediaItemFactory 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 543332020d..c0923bf951 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java @@ -14,6 +14,7 @@ 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.CallTable; +import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.MmsSmsTable; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord; @@ -87,7 +88,7 @@ public class ConversationDataSource implements PagedDataSource referencedIds = new HashSet<>(); - try (MmsSmsTable.Reader reader = MmsSmsTable.readerFor(db.getConversation(threadId, start, length))) { + try (MessageTable.Reader reader = MessageTable.mmsReaderFor(db.getConversation(threadId, start, length))) { MessageRecord record; while ((record = reader.getNext()) != null && !cancellationSignal.isCanceled()) { records.add(record); @@ -194,7 +195,7 @@ public class ConversationDataSource implements PagedDataSource mentions = SignalDatabase.mentions().getMentionsForMessage(messageId.getId()); stopwatch.split("mentions"); - boolean isQuoted = SignalDatabase.mmsSms().isQuoted(record); + boolean isQuoted = SignalDatabase.messages().isQuoted(record); stopwatch.split("is-quoted"); List reactions = SignalDatabase.reactions().getReactions(messageId); @@ -265,7 +266,7 @@ public class ConversationDataSource implements PagedDataSource { - return SignalDatabase.mmsSms().getMessagePositionInConversation(threadId, timestamp, author); + return SignalDatabase.messages().getMessagePositionInConversation(threadId, timestamp, author); }, p -> moveToPosition(p + (isTypingIndicatorShowing() ? 1 : 0), onMessageNotFound)); } @@ -1453,7 +1453,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect @Override public void jumpToMessage(@NonNull MessageRecord messageRecord) { SimpleTask.run(getLifecycle(), () -> { - return SignalDatabase.mmsSms().getMessagePositionInConversation(threadId, + return SignalDatabase.messages().getMessagePositionInConversation(threadId, messageRecord.getDateReceived(), messageRecord.isOutgoing() ? Recipient.self().getId() : messageRecord.getRecipient().getId()); }, p -> moveToPosition(p + (isTypingIndicatorShowing() ? 1 : 0), () -> { @@ -1738,9 +1738,9 @@ public class ConversationFragment extends LoggingFragment implements Multiselect } SimpleTask.run(getLifecycle(), () -> { - return SignalDatabase.mmsSms().getQuotedMessagePosition(threadId, - messageRecord.getQuote().getId(), - messageRecord.getQuote().getAuthor()); + return SignalDatabase.messages().getQuotedMessagePosition(threadId, + messageRecord.getQuote().getId(), + messageRecord.getQuote().getAuthor()); }, p -> moveToPosition(p + (isTypingIndicatorShowing() ? 1 : 0), () -> { Toast.makeText(getContext(), R.string.ConversationFragment_quoted_message_no_longer_available, Toast.LENGTH_SHORT).show(); })); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java index 52821486e6..573bb24e57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java @@ -186,7 +186,7 @@ public class ConversationMessage { */ @WorkerThread public static @NonNull ConversationMessage createWithUnresolvedData(@NonNull Context context, @NonNull MessageRecord messageRecord, @NonNull CharSequence body) { - boolean hasBeenQuoted = SignalDatabase.mmsSms().isQuoted(messageRecord); + boolean hasBeenQuoted = SignalDatabase.messages().isQuoted(messageRecord); if (messageRecord.isMms()) { List mentions = SignalDatabase.mentions().getMentionsForMessage(messageRecord.getId()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java index bb3045ad67..0406df8664 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java @@ -3889,7 +3889,7 @@ public class ConversationParentFragment extends Fragment SimpleTask.run(() -> { //noinspection CodeBlock2Expr - return SignalDatabase.mmsSms().checkMessageExists(reactionDelegate.getMessageRecord()); + return SignalDatabase.messages().checkMessageExists(reactionDelegate.getMessageRecord()); }, messageExists -> { if (!messageExists) { reactionDelegate.hide(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java index 7c54f54c3c..78dcbebfc5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java @@ -59,7 +59,7 @@ class ConversationRepository { @WorkerThread public @NonNull ConversationData getConversationData(long threadId, @NonNull Recipient conversationRecipient, int jumpToPosition) { ThreadTable.ConversationMetadata metadata = SignalDatabase.threads().getConversationMetadata(threadId); - int threadSize = SignalDatabase.mmsSms().getConversationCount(threadId); + int threadSize = SignalDatabase.messages().getMessageCountForThread(threadId); long lastSeen = metadata.getLastSeen(); int lastSeenPosition = 0; long lastScrolled = metadata.getLastScrolled(); @@ -69,7 +69,7 @@ class ConversationRepository { boolean showUniversalExpireTimerUpdate = false; if (lastSeen > 0) { - lastSeenPosition = SignalDatabase.mmsSms().getMessagePositionOnOrAfterTimestamp(threadId, lastSeen); + lastSeenPosition = SignalDatabase.messages().getMessagePositionOnOrAfterTimestamp(threadId, lastSeen); } if (lastSeenPosition <= 0) { @@ -77,7 +77,7 @@ class ConversationRepository { } if (lastSeen == 0 && lastScrolled > 0) { - lastScrolledPosition = SignalDatabase.mmsSms().getMessagePositionOnOrAfterTimestamp(threadId, lastScrolled); + lastScrolledPosition = SignalDatabase.messages().getMessagePositionOnOrAfterTimestamp(threadId, lastScrolled); } if (!isMessageRequestAccepted) { @@ -105,7 +105,7 @@ class ConversationRepository { conversationRecipient.getExpiresInSeconds() == 0 && !conversationRecipient.isGroup() && conversationRecipient.isRegistered() && - (threadId == -1 || !SignalDatabase.mmsSms().hasMeaningfulMessage(threadId))) + (threadId == -1 || !SignalDatabase.messages().hasMeaningfulMessage(threadId))) { showUniversalExpireTimerUpdate = true; } @@ -172,7 +172,7 @@ class ConversationRepository { long threadId = SignalDatabase.threads().getThreadIdIfExistsFor(recipient.getId()); - boolean hasUnexportedInsecureMessages = threadId != -1 && SignalDatabase.mmsSms().getUnexportedInsecureMessagesCount(threadId) > 0; + boolean hasUnexportedInsecureMessages = threadId != -1 && SignalDatabase.messages().getUnexportedInsecureMessagesCount(threadId) > 0; Log.i(TAG, "Returning registered state..."); return new ConversationSecurityInfo(recipient.getId(), @@ -190,7 +190,7 @@ class ConversationRepository { return Observable. create(emitter -> { - DatabaseObserver.Observer listener = () -> emitter.onNext(SignalDatabase.mmsSms().getIncomingMeaningfulMessageCountSince(threadId, afterTime)); + DatabaseObserver.Observer listener = () -> emitter.onNext(SignalDatabase.messages().getIncomingMeaningfulMessageCountSince(threadId, afterTime)); ApplicationDependencies.getDatabaseObserver().registerConversationObserver(threadId, listener); emitter.setCancellable(() -> ApplicationDependencies.getDatabaseObserver().unregisterObserver(listener)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesRepository.kt index fe84fe79b1..f45314c79f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesRepository.kt @@ -27,7 +27,7 @@ class MessageQuotesRepository { */ fun getMessagesInQuoteChain(application: Application, messageId: MessageId): Observable> { return Observable.create { emitter -> - val threadId: Long = SignalDatabase.mmsSms.getThreadId(messageId) + val threadId: Long = SignalDatabase.messages.getThreadIdForMessage(messageId.id) if (threadId < 0) { Log.w(TAG, "Could not find a threadId for $messageId!") emitter.onNext(emptyList()) @@ -46,7 +46,7 @@ class MessageQuotesRepository { @WorkerThread private fun getMessagesInQuoteChainSync(application: Application, messageId: MessageId): List { - val rootMessageId: MessageId = SignalDatabase.mmsSms.getRootOfQuoteChain(messageId) + val rootMessageId: MessageId = SignalDatabase.messages.getRootOfQuoteChain(messageId) var originalRecord: MessageRecord? = SignalDatabase.messages.getMessageRecordOrNull(rootMessageId.id) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index f6b2019ab3..b7a94b7167 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -616,7 +616,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode @Override public void onMessageClicked(@NonNull MessageResult message) { SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> { - int startingPosition = SignalDatabase.mmsSms().getMessagePositionInConversation(message.getThreadId(), message.getReceivedTimestampMs()); + int startingPosition = SignalDatabase.messages().getMessagePositionInConversation(message.getThreadId(), message.getReceivedTimestampMs()); return Math.max(0, startingPosition); }, startingPosition -> { hideKeyboard(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.java index ac8963330a..559ebe82a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.java @@ -52,12 +52,14 @@ import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchSet; import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.documents.NetworkFailureSet; +import org.thoughtcrime.securesms.database.model.DisplayRecord; import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.database.model.MessageExportStatus; import org.thoughtcrime.securesms.database.model.MessageId; import org.thoughtcrime.securesms.database.model.MessageRecord; +import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord; import org.thoughtcrime.securesms.database.model.ParentStoryId; import org.thoughtcrime.securesms.database.model.Quote; @@ -94,6 +96,7 @@ import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; import org.whispersystems.signalservice.api.push.ServiceId; import java.io.Closeable; @@ -102,6 +105,7 @@ import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -114,6 +118,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.Function; import static org.thoughtcrime.securesms.contactshare.Contact.Avatar; @@ -325,6 +330,11 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie private static final String IS_STORY_CLAUSE = STORY_TYPE + " > 0 AND " + REMOTE_DELETED + " = 0"; private static final String RAW_ID_WHERE = TABLE_NAME + "._id = ?"; + private static final String SNIPPET_QUERY = "SELECT " + MessageTable.ID + ", " + MessageTable.TYPE + ", " + MessageTable.DATE_RECEIVED + " FROM " + MessageTable.TABLE_NAME + " " + + "WHERE " + MessageTable.THREAD_ID + " = ? AND " + MessageTable.TYPE + " & " + MessageTypes.GROUP_V2_LEAVE_BITS + " != " + MessageTypes.GROUP_V2_LEAVE_BITS + " AND " + MessageTable.STORY_TYPE + " = 0 AND " + MessageTable.PARENT_STORY_ID + " <= 0 " + + "ORDER BY " + MessageTable.DATE_RECEIVED + " DESC " + + "LIMIT 1"; + private final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache("MmsDelivery"); public MessageTable(Context context, SignalDatabase databaseHelper) { @@ -386,10 +396,6 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie updateTypeBitmask(id, 0, MessageTypes.SECURE_MESSAGE_BIT); } - public void markAsPush(long id) { - updateTypeBitmask(id, 0, MessageTypes.PUSH_MESSAGE_BIT); - } - public void markAsDecryptFailed(long id) { updateTypeBitmask(id, MessageTypes.ENCRYPTION_MASK, MessageTypes.ENCRYPTION_REMOTE_FAILED_BIT); } @@ -489,7 +495,6 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie RecipientId recipientId = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID)); long dateSent = CursorUtil.requireLong(cursor, DATE_SENT); SyncMessageId syncMessageId = new SyncMessageId(recipientId, dateSent); - StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE)); results.add(new MarkedMessageInfo(threadId, syncMessageId, new MessageId(messageId), null)); } @@ -566,7 +571,6 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie RecipientId recipientId = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID)); long dateSent = CursorUtil.requireLong(cursor, DATE_SENT); SyncMessageId syncMessageId = new SyncMessageId(recipientId, dateSent); - StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE)); results.add(new MarkedMessageInfo(threadId, syncMessageId, new MessageId(messageId), null)); @@ -1612,6 +1616,10 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie } public boolean hasMeaningfulMessage(long threadId) { + if (threadId == -1) { + return false; + } + SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); SqlUtil.Query query = buildMeaningfulMessagesQuery(threadId); @@ -1657,90 +1665,6 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie } } - public Set incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType, MessageQualifier messageQualifier) { - SQLiteDatabase database = databaseHelper.getSignalWritableDatabase(); - Set messageUpdates = new HashSet<>(); - - final String qualifierWhere; - switch (messageQualifier) { - case NORMAL: - qualifierWhere = " AND NOT (" + IS_STORY_CLAUSE + ")"; - break; - case STORY: - qualifierWhere = " AND " + IS_STORY_CLAUSE; - break; - case ALL: - qualifierWhere = ""; - break; - default: - throw new IllegalArgumentException("Unsupported qualifier: " + messageQualifier); - } - - try (Cursor cursor = SQLiteDatabaseExtensionsKt.select(database, ID, THREAD_ID, TYPE, RECIPIENT_ID, receiptType.getColumnName(), RECEIPT_TIMESTAMP) - .from(TABLE_NAME) - .where(DATE_SENT + " = ?" + qualifierWhere, messageId.getTimetamp()) - .run()) - { - while (cursor.moveToNext()) { - if (MessageTypes.isOutgoingMessageType(CursorUtil.requireLong(cursor, TYPE))) { - RecipientId theirRecipientId = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID)); - RecipientId ourRecipientId = messageId.getRecipientId(); - String columnName = receiptType.getColumnName(); - - if (ourRecipientId.equals(theirRecipientId) || Recipient.resolved(theirRecipientId).isGroup()) { - long id = CursorUtil.requireLong(cursor, ID); - long threadId = CursorUtil.requireLong(cursor, THREAD_ID); - int status = receiptType.getGroupStatus(); - boolean isFirstIncrement = CursorUtil.requireLong(cursor, columnName) == 0; - long savedTimestamp = CursorUtil.requireLong(cursor, RECEIPT_TIMESTAMP); - long updatedTimestamp = isFirstIncrement ? Math.max(savedTimestamp, timestamp) : savedTimestamp; - - database.execSQL("UPDATE " + TABLE_NAME + " SET " + - columnName + " = " + columnName + " + 1, " + - RECEIPT_TIMESTAMP + " = ? WHERE " + - ID + " = ?", - SqlUtil.buildArgs(updatedTimestamp, id)); - - SignalDatabase.groupReceipts().update(ourRecipientId, id, status, timestamp); - - messageUpdates.add(new MessageUpdate(threadId, new MessageId(id))); - } - } - } - - if (messageUpdates.size() > 0 && receiptType == ReceiptType.DELIVERY) { - earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getRecipientId(), timestamp); - } - } - - messageUpdates.addAll(incrementStoryReceiptCount(messageId, timestamp, receiptType)); - - return messageUpdates; - } - - private Set incrementStoryReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType) { - SQLiteDatabase database = databaseHelper.getSignalWritableDatabase(); - Set messageUpdates = new HashSet<>(); - String columnName = receiptType.getColumnName(); - - for (MessageId storyMessageId : SignalDatabase.storySends().getStoryMessagesFor(messageId)) { - database.execSQL("UPDATE " + TABLE_NAME + " SET " + - columnName + " = " + columnName + " + 1, " + - RECEIPT_TIMESTAMP + " = CASE " + - "WHEN " + columnName + " = 0 THEN MAX(" + RECEIPT_TIMESTAMP + ", ?) " + - "ELSE " + RECEIPT_TIMESTAMP + " " + - "END " + - "WHERE " + ID + " = ?", - SqlUtil.buildArgs(timestamp, storyMessageId.getId())); - - SignalDatabase.groupReceipts().update(messageId.getRecipientId(), storyMessageId.getId(), receiptType.getGroupStatus(), timestamp); - - messageUpdates.add(new MessageUpdate(-1, storyMessageId)); - } - - return messageUpdates; - } - public long getThreadIdForMessage(long id) { String sql = "SELECT " + THREAD_ID + " FROM " + TABLE_NAME + " WHERE " + ID + " = ?"; String[] sqlArgs = new String[] {id+""}; @@ -2095,7 +2019,6 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie long expireStarted = CursorUtil.requireLong(cursor, EXPIRE_STARTED); SyncMessageId syncMessageId = new SyncMessageId(recipientId, dateSent); ExpirationInfo expirationInfo = new ExpirationInfo(messageId, expiresIn, expireStarted, true); - StoryType storyType = StoryType.fromCode(CursorUtil.requireInt(cursor, STORY_TYPE)); if (!recipientId.equals(releaseChannelId)) { result.add(new MarkedMessageInfo(threadId, syncMessageId, new MessageId(messageId), expirationInfo)); @@ -3344,7 +3267,7 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie db.beginTransaction(); try { - try (MmsSmsTable.Reader reader = MmsSmsTable.readerFor(SignalDatabase.mmsSms().getConversation(threadId, 0, 2))) { + try (MessageTable.Reader reader = MessageTable.mmsReaderFor(SignalDatabase.mmsSms().getConversation(threadId, 0, 2))) { MessageRecord latestMessage = reader.getNext(); if (latestMessage != null && latestMessage.isGroupV2()) { Optional changeEditor = message.getChangeEditor(); @@ -3385,7 +3308,7 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie return Util.join(segments, " OR "); } - final int getInsecureMessagesSentForThread(long threadId) { + public final int getInsecureMessageSentCount(long threadId) { SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); String[] projection = new String[]{"COUNT(*)"}; String query = THREAD_ID + " = ? AND " + getOutgoingInsecureMessageClause() + " AND " + DATE_SENT + " > ?"; @@ -3400,7 +3323,7 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie } } - final int getInsecureMessageCountForInsights() { + public final int getInsecureMessageCountForInsights() { return getMessageCountForRecipientsAndType(getOutgoingInsecureMessageClause()); } @@ -3420,11 +3343,11 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie .run(); } - final int getSecureMessageCountForInsights() { + public final int getSecureMessageCountForInsights() { return getMessageCountForRecipientsAndType(getOutgoingSecureMessageClause()); } - final int getSecureMessageCount(long threadId) { + public final int getSecureMessageCount(long threadId) { SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); String[] projection = new String[] {"COUNT(*)"}; String query = getSecureMessageClause() + "AND " + THREAD_ID + " = ?"; @@ -3439,7 +3362,7 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie } } - final int getOutgoingSecureMessageCount(long threadId) { + public final int getOutgoingSecureMessageCount(long threadId) { SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); String[] projection = new String[] {"COUNT(*)"}; String query = getOutgoingSecureMessageClause() + @@ -3456,50 +3379,6 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie } } - /** - * Handles a synchronized read message. - * @param messageId An id representing the author-timestamp pair of the message that was read on a linked device. Note that the author could be self when - * syncing read receipts for reactions. - */ - final @NonNull MmsSmsTable.TimestampReadResult setTimestampReadFromSyncMessage(SyncMessageId messageId, long proposedExpireStarted, @NonNull Map threadToLatestRead) { - SQLiteDatabase database = databaseHelper.getSignalWritableDatabase(); - List> expiring = new LinkedList<>(); - String[] projection = new String[] { ID, THREAD_ID, EXPIRES_IN, EXPIRE_STARTED }; - String query = DATE_SENT + " = ? AND (" + RECIPIENT_ID + " = ? OR (" + RECIPIENT_ID + " = ? AND " + getOutgoingTypeClause() + "))"; - String[] args = SqlUtil.buildArgs(messageId.getTimetamp(), messageId.getRecipientId(), Recipient.self().getId()); - List threads = new LinkedList<>(); - - try (Cursor cursor = database.query(TABLE_NAME, projection, query, args, null, null, null)) { - while (cursor.moveToNext()) { - long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); - long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); - long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN)); - long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED)); - - expireStarted = expireStarted > 0 ? Math.min(proposedExpireStarted, expireStarted) : proposedExpireStarted; - - ContentValues values = new ContentValues(); - values.put(READ, 1); - values.put(REACTIONS_UNREAD, 0); - values.put(REACTIONS_LAST_SEEN, System.currentTimeMillis()); - - if (expiresIn > 0) { - values.put(EXPIRE_STARTED, expireStarted); - expiring.add(new Pair<>(id, expiresIn)); - } - - database.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(id)); - - threads.add(threadId); - - Long latest = threadToLatestRead.get(threadId); - threadToLatestRead.put(threadId, (latest != null) ? Math.max(latest, messageId.getTimetamp()) : messageId.getTimetamp()); - } - } - - return new MmsSmsTable.TimestampReadResult(expiring, threads); - } - private int getMessageCountForRecipientsAndType(String typeClause) { SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); @@ -3704,6 +3583,698 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie } } + /** + * @return The user that added you to the group, otherwise null. + */ + public @Nullable RecipientId getGroupAddedBy(long threadId) { + long lastQuitChecked = System.currentTimeMillis(); + Pair pair; + + do { + pair = getGroupAddedBy(threadId, lastQuitChecked); + if (pair.first() != null) { + return pair.first(); + } else { + lastQuitChecked = pair.second(); + } + + } while (pair.second() != -1); + + return null; + } + + private @NonNull Pair getGroupAddedBy(long threadId, long lastQuitChecked) { + long latestQuit = SignalDatabase.messages().getLatestGroupQuitTimestamp(threadId, lastQuitChecked); + RecipientId id = SignalDatabase.messages().getOldestGroupUpdateSender(threadId, latestQuit); + + return new Pair<>(id, latestQuit); + } + + /** + * Whether or not the message has been quoted by another message. + */ + public boolean isQuoted(@NonNull MessageRecord messageRecord) { + RecipientId author = messageRecord.isOutgoing() ? Recipient.self().getId() : messageRecord.getRecipient().getId(); + long timestamp = messageRecord.getDateSent(); + + String where = MessageTable.QUOTE_ID + " = ? AND " + MessageTable.QUOTE_AUTHOR + " = ?"; + String[] whereArgs = SqlUtil.buildArgs(timestamp, author); + + try (Cursor cursor = getReadableDatabase().query(MessageTable.TABLE_NAME, new String[]{ "1" }, where, whereArgs, null, null, null, "1")) { + return cursor.moveToFirst(); + } + } + + /** + * Given a collection of MessageRecords, this will return a set of the IDs of the records that have been quoted by another message. + * Does an efficient bulk lookup that makes it faster than {@link #isQuoted(MessageRecord)} for multiple records. + */ + public Set isQuoted(@NonNull Collection records) { + if (records.isEmpty()) { + return Collections.emptySet(); + } + + Map byQuoteDescriptor = new HashMap<>(records.size()); + List args = new ArrayList<>(records.size()); + + for (MessageRecord record : records) { + long timestamp = record.getDateSent(); + RecipientId author = record.isOutgoing() ? Recipient.self().getId() : record.getRecipient().getId(); + + byQuoteDescriptor.put(new QuoteDescriptor(timestamp, author), record); + args.add(SqlUtil.buildArgs(timestamp, author)); + } + + + String[] projection = new String[] { QUOTE_ID, QUOTE_AUTHOR }; + List queries = SqlUtil.buildCustomCollectionQuery(QUOTE_ID + " = ? AND " + QUOTE_AUTHOR + " = ?", args); + Set quotedIds = new HashSet<>(); + + for (SqlUtil.Query query : queries) { + try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, projection, query.getWhere(), query.getWhereArgs(), null, null, null)) { + while (cursor.moveToNext()) { + long timestamp = CursorUtil.requireLong(cursor, QUOTE_ID); + RecipientId author = RecipientId.from(CursorUtil.requireString(cursor, QUOTE_AUTHOR)); + QuoteDescriptor quoteLocator = new QuoteDescriptor(timestamp, author); + + quotedIds.add(byQuoteDescriptor.get(quoteLocator).getId()); + } + } + } + + return quotedIds; + } + + public MessageId getRootOfQuoteChain(@NonNull MessageId id) { + MmsMessageRecord targetMessage; + try { + targetMessage = (MmsMessageRecord) SignalDatabase.messages().getMessageRecord(id.getId()); + } catch (NoSuchMessageException e) { + throw new IllegalArgumentException("Invalid message ID!"); + } + + if (targetMessage.getQuote() == null) { + return id; + } + + String query; + if (targetMessage.getQuote().getAuthor().equals(Recipient.self().getId())) { + query = DATE_SENT + " = " + targetMessage.getQuote().getId() + " AND (" + TYPE + " & " + MessageTypes.BASE_TYPE_MASK + ") = " + MessageTypes.BASE_SENT_TYPE; + } else { + query = DATE_SENT + " = " + targetMessage.getQuote().getId() + " AND " + RECIPIENT_ID + " = '" + targetMessage.getQuote().getAuthor().serialize() + "'"; + } + + try (Reader reader = new MmsReader(getReadableDatabase().query(TABLE_NAME, MMS_PROJECTION, query, null, null, null, "1"))) { + MessageRecord record; + if ((record = reader.getNext()) != null) { + return getRootOfQuoteChain(new MessageId(record.getId())); + } + } + + return id; + } + + public int getQuotedMessagePosition(long threadId, long quoteId, @NonNull RecipientId recipientId) { + String[] projection = new String[]{ DATE_SENT, RECIPIENT_ID, REMOTE_DELETED}; + String order = DATE_RECEIVED + " DESC"; + String selection = THREAD_ID + " = " + threadId + " AND " + STORY_TYPE + " = 0" + " AND " + PARENT_STORY_ID + " <= 0"; + + try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, projection, selection, null, null, null, order)) { + boolean isOwnNumber = Recipient.resolved(recipientId).isSelf(); + + while (cursor != null && cursor.moveToNext()) { + boolean quoteIdMatches = cursor.getLong(0) == quoteId; + boolean recipientIdMatches = recipientId.equals(RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID))); + + if (quoteIdMatches && (recipientIdMatches || isOwnNumber)) { + if (CursorUtil.requireBoolean(cursor, REMOTE_DELETED)) { + return -1; + } else { + return cursor.getPosition(); + } + } + } + } + return -1; + } + + public int getMessagePositionInConversation(long threadId, long receivedTimestamp, @NonNull RecipientId recipientId) { + String[] projection = new String[]{ DATE_RECEIVED, RECIPIENT_ID, REMOTE_DELETED}; + String order = DATE_RECEIVED + " DESC"; + String selection = THREAD_ID + " = " + threadId + " AND " + STORY_TYPE + " = 0" + " AND " + PARENT_STORY_ID + " <= 0"; + + try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, projection, selection, null, null, null, order)) { + boolean isOwnNumber = Recipient.resolved(recipientId).isSelf(); + + while (cursor != null && cursor.moveToNext()) { + boolean timestampMatches = cursor.getLong(0) == receivedTimestamp; + boolean recipientIdMatches = recipientId.equals(RecipientId.from(cursor.getLong(1))); + + if (timestampMatches && (recipientIdMatches || isOwnNumber)) { + if (CursorUtil.requireBoolean(cursor, REMOTE_DELETED)) { + return -1; + } else { + return cursor.getPosition(); + } + } + } + } + return -1; + } + + public int getMessagePositionInConversation(long threadId, long receivedTimestamp) { + return getMessagePositionInConversation(threadId, 0, receivedTimestamp); + } + + /** + * Retrieves the position of the message with the provided timestamp in the query results you'd + * get from calling {@link MmsSmsTable#getConversation(long)}. + * + * Note: This could give back incorrect results in the situation where multiple messages have the + * same received timestamp. However, because this was designed to determine where to scroll to, + * you'll still wind up in about the right spot. + * + * @param groupStoryId Ignored if passed value is <= 0 + */ + public int getMessagePositionInConversation(long threadId, long groupStoryId, long receivedTimestamp) { + final String order; + final String selection; + + if (groupStoryId > 0) { + order = MessageTable.DATE_RECEIVED + " ASC"; + selection = MessageTable.THREAD_ID + " = " + threadId + " AND " + + MessageTable.DATE_RECEIVED + " < " + receivedTimestamp + " AND " + + MessageTable.STORY_TYPE + " = 0 AND " + MessageTable.PARENT_STORY_ID + " = " + groupStoryId; + } else { + order = MessageTable.DATE_RECEIVED + " DESC"; + selection = MessageTable.THREAD_ID + " = " + threadId + " AND " + + MessageTable.DATE_RECEIVED + " > " + receivedTimestamp + " AND " + + MessageTable.STORY_TYPE + " = 0 AND " + MessageTable.PARENT_STORY_ID + " <= 0"; + } + + try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, new String[] { "COUNT(*)" }, selection, null, null, null, order)) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getInt(0); + } + } + return -1; + } + + public long getTimestampForFirstMessageAfterDate(long date) { + String[] projection = new String[] { MessageTable.DATE_RECEIVED }; + String order = MessageTable.DATE_RECEIVED + " ASC"; + String selection = MessageTable.DATE_RECEIVED + " > " + date; + + try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, projection, selection, null, null, order, "1")) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(0); + } + } + + return 0; + } + + public int getMessageCountBeforeDate(long date) { + String selection = MessageTable.DATE_RECEIVED + " < " + date; + + try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, COUNT, selection, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getInt(0); + } + } + + return 0; + } + + public @NonNull List getMessagesAfterVoiceNoteInclusive(long messageId, long limit) throws NoSuchMessageException { + MessageRecord origin = getMessageRecord(messageId); + List mms = getMessagesInThreadAfterInclusive(origin.getThreadId(), origin.getDateReceived(), limit); + + Collections.sort(mms, Comparator.comparingLong(DisplayRecord::getDateReceived)); + + return Stream.of(mms).limit(limit).toList(); + } + + public int getMessagePositionOnOrAfterTimestamp(long threadId, long timestamp) { + String[] projection = new String[] { "COUNT(*)" }; + String selection = MessageTable.THREAD_ID + " = " + threadId + " AND " + + MessageTable.DATE_RECEIVED + " >= " + timestamp + " AND " + + MessageTable.STORY_TYPE + " = 0 AND " + MessageTable.PARENT_STORY_ID + " <= 0"; + + try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, projection, selection, null, null, null, null)) { + if (cursor != null && cursor.moveToNext()) { + return cursor.getInt(0); + } + } + return 0; + } + + public long getConversationSnippetType(long threadId) throws NoSuchMessageException { + SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); + try (Cursor cursor = db.rawQuery(SNIPPET_QUERY, SqlUtil.buildArgs(threadId))) { + if (cursor.moveToFirst()) { + return CursorUtil.requireLong(cursor, MessageTable.TYPE); + } else { + throw new NoSuchMessageException("no message"); + } + } + } + + public @NonNull MessageRecord getConversationSnippet(long threadId) throws NoSuchMessageException { + try (Cursor cursor = getConversationSnippetCursor(threadId)) { + if (cursor.moveToFirst()) { + long id = CursorUtil.requireLong(cursor, MessageTable.ID); + return SignalDatabase.messages().getMessageRecord(id); + } else { + throw new NoSuchMessageException("no message"); + } + } + } + + @VisibleForTesting + @NonNull Cursor getConversationSnippetCursor(long threadId) { + SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); + return db.rawQuery(SNIPPET_QUERY, SqlUtil.buildArgs(threadId)); + } + + public int getUnreadCount(long threadId) { + String selection = READ + " = 0 AND " + STORY_TYPE + " = 0 AND " + THREAD_ID + " = " + threadId + " AND " + PARENT_STORY_ID + " <= 0"; + + try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, COUNT, selection, null, null, null, null)) { + if (cursor.moveToFirst()) { + return cursor.getInt(0); + } else { + return 0; + } + } + } + + public boolean checkMessageExists(@NonNull MessageRecord messageRecord) { + return SQLiteDatabaseExtensionsKt + .exists(getReadableDatabase(), TABLE_NAME) + .where(ID + " = ?", messageRecord.getId()) + .run(); + } + + public @NonNull List getReportSpamMessageServerData(long threadId, long timestamp, int limit) { + return SignalDatabase + .messages() + .getReportSpamMessageServerGuids(threadId, timestamp) + .stream() + .sorted((l, r) -> -Long.compare(l.getDateReceived(), r.getDateReceived())) + .limit(limit) + .collect(java.util.stream.Collectors.toList()); + } + + private @NonNull MessageExportState getMessageExportState(@NonNull MessageId messageId) throws NoSuchMessageException { + String table = MessageTable.TABLE_NAME; + String[] projection = SqlUtil.buildArgs(MessageTable.EXPORT_STATE); + String[] args = SqlUtil.buildArgs(messageId.getId()); + + try (Cursor cursor = getReadableDatabase().query(table, projection, ID_WHERE, args, null, null, null, null)) { + if (cursor.moveToFirst()) { + byte[] bytes = CursorUtil.requireBlob(cursor, MessageTable.EXPORT_STATE); + if (bytes == null) { + return MessageExportState.getDefaultInstance(); + } else { + try { + return MessageExportState.parseFrom(bytes); + } catch (InvalidProtocolBufferException e) { + return MessageExportState.getDefaultInstance(); + } + } + } else { + throw new NoSuchMessageException("The requested message does not exist."); + } + } + } + + public void updateMessageExportState(@NonNull MessageId messageId, @NonNull Function transform) throws NoSuchMessageException { + SQLiteDatabase database = getWritableDatabase(); + + database.beginTransaction(); + try { + MessageExportState oldState = getMessageExportState(messageId); + MessageExportState newState = transform.apply(oldState); + + setMessageExportState(messageId, newState); + + database.setTransactionSuccessful(); + } finally { + database.endTransaction(); + } + } + + public void markMessageExported(@NonNull MessageId messageId) { + ContentValues contentValues = new ContentValues(1); + + contentValues.put(MessageTable.EXPORTED, MessageExportStatus.EXPORTED.getCode()); + + getWritableDatabase().update(TABLE_NAME, contentValues, ID_WHERE, SqlUtil.buildArgs(messageId.getId())); + } + + public void markMessageExportFailed(@NonNull MessageId messageId) { + ContentValues contentValues = new ContentValues(1); + + contentValues.put(MessageTable.EXPORTED, MessageExportStatus.ERROR.getCode()); + + getWritableDatabase().update(TABLE_NAME, contentValues, ID_WHERE, SqlUtil.buildArgs(messageId.getId())); + } + + private void setMessageExportState(@NonNull MessageId messageId, @NonNull MessageExportState messageExportState) { + ContentValues contentValues = new ContentValues(1); + + contentValues.put(MessageTable.EXPORT_STATE, messageExportState.toByteArray()); + + getWritableDatabase().update(TABLE_NAME, contentValues, ID_WHERE, SqlUtil.buildArgs(messageId.getId())); + } + + public Collection incrementDeliveryReceiptCounts(@NonNull List syncMessageIds, long timestamp) { + return incrementReceiptCounts(syncMessageIds, timestamp, ReceiptType.DELIVERY); + } + + public boolean incrementDeliveryReceiptCount(SyncMessageId syncMessageId, long timestamp) { + return incrementReceiptCount(syncMessageId, timestamp, ReceiptType.DELIVERY); + } + + /** + * @return A list of ID's that were not updated. + */ + public @NonNull Collection incrementReadReceiptCounts(@NonNull List syncMessageIds, long timestamp) { + return incrementReceiptCounts(syncMessageIds, timestamp, ReceiptType.READ); + } + + public boolean incrementReadReceiptCount(SyncMessageId syncMessageId, long timestamp) { + return incrementReceiptCount(syncMessageId, timestamp, ReceiptType.READ); + } + + /** + * @return A list of ID's that were not updated. + */ + public @NonNull Collection incrementViewedReceiptCounts(@NonNull List syncMessageIds, long timestamp) { + return incrementReceiptCounts(syncMessageIds, timestamp, ReceiptType.VIEWED); + } + + public @NonNull Collection incrementViewedNonStoryReceiptCounts(@NonNull List syncMessageIds, long timestamp) { + return incrementReceiptCounts(syncMessageIds, timestamp, ReceiptType.VIEWED, MessageQualifier.NORMAL); + } + + public boolean incrementViewedReceiptCount(SyncMessageId syncMessageId, long timestamp) { + return incrementReceiptCount(syncMessageId, timestamp, ReceiptType.VIEWED); + } + + public @NonNull Collection incrementViewedStoryReceiptCounts(@NonNull List syncMessageIds, long timestamp) { + SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); + Set messageUpdates = new HashSet<>(); + Collection unhandled = new HashSet<>(); + + db.beginTransaction(); + try { + for (SyncMessageId id : syncMessageIds) { + Set updates = incrementReceiptCountInternal(id, timestamp, ReceiptType.VIEWED, MessageQualifier.STORY); + + if (updates.size() > 0) { + messageUpdates.addAll(updates); + } else { + unhandled.add(id); + } + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + + for (MessageUpdate update : messageUpdates) { + ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(update.getMessageId()); + ApplicationDependencies.getDatabaseObserver().notifyVerboseConversationListeners(Collections.singleton(update.getThreadId())); + } + + if (messageUpdates.size() > 0) { + notifyConversationListListeners(); + } + } + + return unhandled; + } + + /** + * Wraps a single receipt update in a transaction and triggers the proper updates. + * + * @return Whether or not some thread was updated. + */ + private boolean incrementReceiptCount(SyncMessageId syncMessageId, long timestamp, @NonNull MessageTable.ReceiptType receiptType) { + return incrementReceiptCount(syncMessageId, timestamp, receiptType, MessageTable.MessageQualifier.ALL); + } + + private boolean incrementReceiptCount(SyncMessageId syncMessageId, long timestamp, @NonNull MessageTable.ReceiptType receiptType, @NonNull MessageTable.MessageQualifier messageQualifier) { + SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); + ThreadTable threadTable = SignalDatabase.threads(); + Set messageUpdates = new HashSet<>(); + + db.beginTransaction(); + try { + messageUpdates = incrementReceiptCountInternal(syncMessageId, timestamp, receiptType, messageQualifier); + + for (MessageUpdate messageUpdate : messageUpdates) { + threadTable.update(messageUpdate.getThreadId(), false); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + + for (MessageUpdate threadUpdate : messageUpdates) { + ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(threadUpdate.getMessageId()); + } + } + + return messageUpdates.size() > 0; + } + + /** + * Wraps multiple receipt updates in a transaction and triggers the proper updates. + * + * @return All of the messages that didn't result in updates. + */ + private @NonNull Collection incrementReceiptCounts(@NonNull List syncMessageIds, long timestamp, @NonNull MessageTable.ReceiptType receiptType) { + return incrementReceiptCounts(syncMessageIds, timestamp, receiptType, MessageTable.MessageQualifier.ALL); + } + + private @NonNull Collection incrementReceiptCounts(@NonNull List syncMessageIds, long timestamp, @NonNull MessageTable.ReceiptType receiptType, @NonNull MessageTable.MessageQualifier messageQualifier) { + SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); + ThreadTable threadTable = SignalDatabase.threads(); + Set messageUpdates = new HashSet<>(); + Collection unhandled = new HashSet<>(); + + db.beginTransaction(); + try { + for (SyncMessageId id : syncMessageIds) { + Set updates = incrementReceiptCountInternal(id, timestamp, receiptType, messageQualifier); + + if (updates.size() > 0) { + messageUpdates.addAll(updates); + } else { + unhandled.add(id); + } + } + + for (MessageUpdate update : messageUpdates) { + threadTable.updateSilently(update.getThreadId(), false); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + + for (MessageUpdate update : messageUpdates) { + ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(update.getMessageId()); + ApplicationDependencies.getDatabaseObserver().notifyVerboseConversationListeners(Collections.singleton(update.getThreadId())); + } + + if (messageUpdates.size() > 0) { + notifyConversationListListeners(); + } + } + + return unhandled; + } + + private @NonNull Set incrementReceiptCountInternal(SyncMessageId messageId, long timestamp, MessageTable.ReceiptType receiptType, @NonNull MessageTable.MessageQualifier messageQualifier) { + SQLiteDatabase database = databaseHelper.getSignalWritableDatabase(); + Set messageUpdates = new HashSet<>(); + + final String qualifierWhere; + switch (messageQualifier) { + case NORMAL: + qualifierWhere = " AND NOT (" + IS_STORY_CLAUSE + ")"; + break; + case STORY: + qualifierWhere = " AND " + IS_STORY_CLAUSE; + break; + case ALL: + qualifierWhere = ""; + break; + default: + throw new IllegalArgumentException("Unsupported qualifier: " + messageQualifier); + } + + try (Cursor cursor = SQLiteDatabaseExtensionsKt.select(database, ID, THREAD_ID, TYPE, RECIPIENT_ID, receiptType.getColumnName(), RECEIPT_TIMESTAMP) + .from(TABLE_NAME) + .where(DATE_SENT + " = ?" + qualifierWhere, messageId.getTimetamp()) + .run()) + { + while (cursor.moveToNext()) { + if (MessageTypes.isOutgoingMessageType(CursorUtil.requireLong(cursor, TYPE))) { + RecipientId theirRecipientId = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID)); + RecipientId ourRecipientId = messageId.getRecipientId(); + String columnName = receiptType.getColumnName(); + + if (ourRecipientId.equals(theirRecipientId) || Recipient.resolved(theirRecipientId).isGroup()) { + long id = CursorUtil.requireLong(cursor, ID); + long threadId = CursorUtil.requireLong(cursor, THREAD_ID); + int status = receiptType.getGroupStatus(); + boolean isFirstIncrement = CursorUtil.requireLong(cursor, columnName) == 0; + long savedTimestamp = CursorUtil.requireLong(cursor, RECEIPT_TIMESTAMP); + long updatedTimestamp = isFirstIncrement ? Math.max(savedTimestamp, timestamp) : savedTimestamp; + + database.execSQL("UPDATE " + TABLE_NAME + " SET " + + columnName + " = " + columnName + " + 1, " + + RECEIPT_TIMESTAMP + " = ? WHERE " + + ID + " = ?", + SqlUtil.buildArgs(updatedTimestamp, id)); + + SignalDatabase.groupReceipts().update(ourRecipientId, id, status, timestamp); + + messageUpdates.add(new MessageUpdate(threadId, new MessageId(id))); + } + } + } + + if (messageUpdates.size() > 0 && receiptType == ReceiptType.DELIVERY) { + earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getRecipientId(), timestamp); + } + } + + messageUpdates.addAll(incrementStoryReceiptCount(messageId, timestamp, receiptType)); + + return messageUpdates; + } + + private Set incrementStoryReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType) { + SQLiteDatabase database = databaseHelper.getSignalWritableDatabase(); + Set messageUpdates = new HashSet<>(); + String columnName = receiptType.getColumnName(); + + for (MessageId storyMessageId : SignalDatabase.storySends().getStoryMessagesFor(messageId)) { + database.execSQL("UPDATE " + TABLE_NAME + " SET " + + columnName + " = " + columnName + " + 1, " + + RECEIPT_TIMESTAMP + " = CASE " + + "WHEN " + columnName + " = 0 THEN MAX(" + RECEIPT_TIMESTAMP + ", ?) " + + "ELSE " + RECEIPT_TIMESTAMP + " " + + "END " + + "WHERE " + ID + " = ?", + SqlUtil.buildArgs(timestamp, storyMessageId.getId())); + + SignalDatabase.groupReceipts().update(messageId.getRecipientId(), storyMessageId.getId(), receiptType.getGroupStatus(), timestamp); + + messageUpdates.add(new MessageUpdate(-1, storyMessageId)); + } + + return messageUpdates; + } + + /** + * @return Unhandled ids + */ + public Collection setTimestampReadFromSyncMessage(@NonNull List readMessages, long proposedExpireStarted, @NonNull Map threadToLatestRead) { + SQLiteDatabase db = getWritableDatabase(); + + List> expiringMessages = new LinkedList<>(); + Set updatedThreads = new HashSet<>(); + Collection unhandled = new LinkedList<>(); + + db.beginTransaction(); + try { + for (ReadMessage readMessage : readMessages) { + RecipientId authorId = Recipient.externalPush(readMessage.getSender()).getId(); + TimestampReadResult result = setTimestampReadFromSyncMessageInternal(new SyncMessageId(authorId, readMessage.getTimestamp()), + proposedExpireStarted, + threadToLatestRead); + + expiringMessages.addAll(result.expiring); + updatedThreads.addAll(result.threads); + + if (result.threads.isEmpty()) { + unhandled.add(new SyncMessageId(authorId, readMessage.getTimestamp())); + } + } + + for (long threadId : updatedThreads) { + SignalDatabase.threads().updateReadState(threadId); + SignalDatabase.threads().setLastSeen(threadId); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + for (Pair expiringMessage : expiringMessages) { + ApplicationDependencies.getExpiringMessageManager() + .scheduleDeletion(expiringMessage.first(), true, proposedExpireStarted, expiringMessage.second()); + } + + for (long threadId : updatedThreads) { + notifyConversationListeners(threadId); + } + + return unhandled; + } + + /** + * Handles a synchronized read message. + * @param messageId An id representing the author-timestamp pair of the message that was read on a linked device. Note that the author could be self when + * syncing read receipts for reactions. + */ + private final @NonNull TimestampReadResult setTimestampReadFromSyncMessageInternal(SyncMessageId messageId, long proposedExpireStarted, @NonNull Map threadToLatestRead) { + SQLiteDatabase database = databaseHelper.getSignalWritableDatabase(); + List> expiring = new LinkedList<>(); + String[] projection = new String[] { ID, THREAD_ID, EXPIRES_IN, EXPIRE_STARTED }; + String query = DATE_SENT + " = ? AND (" + RECIPIENT_ID + " = ? OR (" + RECIPIENT_ID + " = ? AND " + getOutgoingTypeClause() + "))"; + String[] args = SqlUtil.buildArgs(messageId.getTimetamp(), messageId.getRecipientId(), Recipient.self().getId()); + List threads = new LinkedList<>(); + + try (Cursor cursor = database.query(TABLE_NAME, projection, query, args, null, null, null)) { + while (cursor.moveToNext()) { + long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); + long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); + long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN)); + long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED)); + + expireStarted = expireStarted > 0 ? Math.min(proposedExpireStarted, expireStarted) : proposedExpireStarted; + + ContentValues values = new ContentValues(); + values.put(READ, 1); + values.put(REACTIONS_UNREAD, 0); + values.put(REACTIONS_LAST_SEEN, System.currentTimeMillis()); + + if (expiresIn > 0) { + values.put(EXPIRE_STARTED, expireStarted); + expiring.add(new Pair<>(id, expiresIn)); + } + + database.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(id)); + + threads.add(threadId); + + Long latest = threadToLatestRead.get(threadId); + threadToLatestRead.put(threadId, (latest != null) ? Math.max(latest, messageId.getTimetamp()) : messageId.getTimetamp()); + } + } + + return new TimestampReadResult(expiring, threads); + } + @Override public void remapRecipient(@NonNull RecipientId fromId, @NonNull RecipientId toId) { ContentValues values = new ContentValues(); @@ -3812,13 +4383,8 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie private D getDocument(SQLiteDatabase database, long messageId, String column, Class clazz) { - Cursor cursor = null; - - try { - cursor = database.query(TABLE_NAME, new String[] {column}, - ID_WHERE, new String[] {String.valueOf(messageId)}, - null, null, null); + try (Cursor cursor = database.query(TABLE_NAME, new String[] { column }, ID_WHERE, new String[] { String.valueOf(messageId) }, null, null, null)) { if (cursor != null && cursor.moveToNext()) { String document = cursor.getString(cursor.getColumnIndexOrThrow(column)); @@ -3833,32 +4399,13 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie try { return clazz.newInstance(); - } catch (InstantiationException e) { - throw new AssertionError(e); - } catch (IllegalAccessException e) { + } catch (InstantiationException | IllegalAccessException e) { throw new AssertionError(e); } - } finally { - if (cursor != null) - cursor.close(); } } - private long getThreadId(@NonNull SQLiteDatabase db, long messageId) { - String[] projection = new String[]{ THREAD_ID }; - String query = ID + " = ?"; - String[] args = new String[]{ String.valueOf(messageId) }; - - try (Cursor cursor = db.query(TABLE_NAME, projection, query, args, null, null, null)) { - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); - } - } - - return -1; - } - protected enum ReceiptType { READ(READ_RECEIPT_COUNT, GroupReceiptTable.STATUS_READ), DELIVERY(DELIVERY_RECEIPT_COUNT, GroupReceiptTable.STATUS_DELIVERED), @@ -4123,6 +4670,39 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie } } + private static class QuoteDescriptor { + private final long timestamp; + private final RecipientId author; + + private QuoteDescriptor(long timestamp, RecipientId author) { + this.author = author; + this.timestamp = timestamp; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final QuoteDescriptor that = (QuoteDescriptor) o; + return timestamp == that.timestamp && author.equals(that.author); + } + + @Override + public int hashCode() { + return Objects.hash(author, timestamp); + } + } + + static final class TimestampReadResult { + final List> expiring; + final List threads; + + TimestampReadResult(@NonNull List> expiring, @NonNull List threads) { + this.expiring = expiring; + this.threads = threads; + } + } + /** * Describes which messages to act on. This is used when incrementing receipts. * Specifically, this was added to support stories having separate viewed receipt settings. diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsTable.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsTable.java index efe692164d..2a4718adf2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsTable.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsTable.java @@ -16,51 +16,26 @@ */ package org.thoughtcrime.securesms.database; -import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import com.annimon.stream.Stream; -import com.google.protobuf.InvalidProtocolBufferException; import net.zetetic.database.sqlcipher.SQLiteQueryBuilder; -import org.signal.core.util.CursorUtil; import org.signal.core.util.SqlUtil; import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.util.Pair; -import org.thoughtcrime.securesms.database.MessageTable.MessageUpdate; -import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId; -import org.thoughtcrime.securesms.database.model.DisplayRecord; -import org.thoughtcrime.securesms.database.model.MessageExportStatus; import org.thoughtcrime.securesms.database.model.MessageId; import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.database.model.MmsMessageRecord; -import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExportState; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.notifications.v2.DefaultMessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; -import java.io.Closeable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; -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.function.Function; -import java.util.stream.Collectors; public class MmsSmsTable extends DatabaseTable { @@ -112,61 +87,15 @@ public class MmsSmsTable extends DatabaseTable { MessageTable.STORY_TYPE, MessageTable.PARENT_STORY_ID}; - private static final String SNIPPET_QUERY = "SELECT " + MessageTable.ID + ", " + MessageTable.TYPE + ", " + MessageTable.DATE_RECEIVED + " FROM " + MessageTable.TABLE_NAME + " " + - "WHERE " + MessageTable.THREAD_ID + " = ? AND " + MessageTable.TYPE + " & " + MessageTypes.GROUP_V2_LEAVE_BITS + " != " + MessageTypes.GROUP_V2_LEAVE_BITS + " AND " + MessageTable.STORY_TYPE + " = 0 AND " + MessageTable.PARENT_STORY_ID + " <= 0 " + - "ORDER BY " + MessageTable.DATE_RECEIVED + " DESC " + - "LIMIT 1"; - public MmsSmsTable(Context context, SignalDatabase databaseHelper) { super(context, databaseHelper); } - /** - * @return The user that added you to the group, otherwise null. - */ - public @Nullable RecipientId getGroupAddedBy(long threadId) { - long lastQuitChecked = System.currentTimeMillis(); - Pair pair; - - do { - pair = getGroupAddedBy(threadId, lastQuitChecked); - if (pair.first() != null) { - return pair.first(); - } else { - lastQuitChecked = pair.second(); - } - - } while (pair.second() != -1); - - return null; - } - - private @NonNull Pair getGroupAddedBy(long threadId, long lastQuitChecked) { - long latestQuit = SignalDatabase.messages().getLatestGroupQuitTimestamp(threadId, lastQuitChecked); - RecipientId id = SignalDatabase.messages().getOldestGroupUpdateSender(threadId, latestQuit); - - return new Pair<>(id, latestQuit); - } - - public int getMessagePositionOnOrAfterTimestamp(long threadId, long timestamp) { - String[] projection = new String[] { "COUNT(*)" }; - String selection = MessageTable.THREAD_ID + " = " + threadId + " AND " + - MessageTable.DATE_RECEIVED + " >= " + timestamp + " AND " + - MessageTable.STORY_TYPE + " = 0 AND " + MessageTable.PARENT_STORY_ID + " <= 0"; - - try (Cursor cursor = queryTables(projection, selection, null, null, false)) { - if (cursor != null && cursor.moveToNext()) { - return cursor.getInt(0); - } - } - return 0; - } - public @Nullable MessageRecord getMessageFor(long timestamp, RecipientId authorId) { Recipient author = Recipient.resolved(authorId); try (Cursor cursor = queryTables(PROJECTION, MessageTable.DATE_SENT + " = " + timestamp, null, null, true)) { - MmsSmsTable.Reader reader = readerFor(cursor); + MessageTable.Reader reader = MessageTable.mmsReaderFor(cursor); MessageRecord messageRecord; @@ -182,16 +111,6 @@ public class MmsSmsTable extends DatabaseTable { return null; } - public @NonNull List getMessagesAfterVoiceNoteInclusive(long messageId, long limit) throws NoSuchMessageException { - MessageRecord origin = SignalDatabase.messages().getMessageRecord(messageId); - List mms = SignalDatabase.messages().getMessagesInThreadAfterInclusive(origin.getThreadId(), origin.getDateReceived(), limit); - - Collections.sort(mms, Comparator.comparingLong(DisplayRecord::getDateReceived)); - - return Stream.of(mms).limit(limit).toList(); - } - - public Cursor getConversation(long threadId, long offset, long limit) { SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); String order = MessageTable.DATE_RECEIVED + " DESC"; @@ -205,35 +124,6 @@ public class MmsSmsTable extends DatabaseTable { public Cursor getConversation(long threadId) { return getConversation(threadId, 0, 0); } - - public @NonNull MessageRecord getConversationSnippet(long threadId) throws NoSuchMessageException { - try (Cursor cursor = getConversationSnippetCursor(threadId)) { - if (cursor.moveToFirst()) { - long id = CursorUtil.requireLong(cursor, MessageTable.ID); - return SignalDatabase.messages().getMessageRecord(id); - } else { - throw new NoSuchMessageException("no message"); - } - } - } - - @VisibleForTesting - @NonNull Cursor getConversationSnippetCursor(long threadId) { - SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); - return db.rawQuery(SNIPPET_QUERY, SqlUtil.buildArgs(threadId)); - } - - public long getConversationSnippetType(long threadId) throws NoSuchMessageException { - SQLiteDatabase db = databaseHelper.getSignalReadableDatabase(); - try (Cursor cursor = db.rawQuery(SNIPPET_QUERY, SqlUtil.buildArgs(threadId))) { - if (cursor.moveToFirst()) { - return CursorUtil.requireLong(cursor, MessageTable.TYPE); - } else { - throw new NoSuchMessageException("no message"); - } - } - } - public Cursor getMessagesForNotificationState(Collection stickyThreads) { StringBuilder stickyQuery = new StringBuilder(); for (DefaultMessageNotifier.StickyThread stickyThread : stickyThreads) { @@ -257,86 +147,6 @@ public class MmsSmsTable extends DatabaseTable { return queryTables(PROJECTION, selection, order, null, true); } - public Set isQuoted(@NonNull Collection records) { - if (records.isEmpty()) { - return Collections.emptySet(); - } - - Map byQuoteDescriptor = new HashMap<>(records.size()); - List args = new ArrayList<>(records.size()); - - for (MessageRecord record : records) { - long timestamp = record.getDateSent(); - RecipientId author = record.isOutgoing() ? Recipient.self().getId() : record.getRecipient().getId(); - - byQuoteDescriptor.put(new QuoteDescriptor(timestamp, author), record); - args.add(SqlUtil.buildArgs(timestamp, author)); - } - - - String[] projection = new String[] { MessageTable.QUOTE_ID, MessageTable.QUOTE_AUTHOR }; - List queries = SqlUtil.buildCustomCollectionQuery(MessageTable.QUOTE_ID + " = ? AND " + MessageTable.QUOTE_AUTHOR + " = ?", args); - Set quotedIds = new HashSet<>(); - - for (SqlUtil.Query query : queries) { - try (Cursor cursor = getReadableDatabase().query(MessageTable.TABLE_NAME, projection, query.getWhere(), query.getWhereArgs(), null, null, null)) { - while (cursor.moveToNext()) { - long timestamp = CursorUtil.requireLong(cursor, MessageTable.QUOTE_ID); - RecipientId author = RecipientId.from(CursorUtil.requireString(cursor, MessageTable.QUOTE_AUTHOR)); - QuoteDescriptor quoteLocator = new QuoteDescriptor(timestamp, author); - - quotedIds.add(byQuoteDescriptor.get(quoteLocator).getId()); - } - } - } - - return quotedIds; - } - - /** - * Whether or not the message has been quoted by another message. - */ - public boolean isQuoted(@NonNull MessageRecord messageRecord) { - RecipientId author = messageRecord.isOutgoing() ? Recipient.self().getId() : messageRecord.getRecipient().getId(); - long timestamp = messageRecord.getDateSent(); - - String where = MessageTable.QUOTE_ID + " = ? AND " + MessageTable.QUOTE_AUTHOR + " = ?"; - String[] whereArgs = SqlUtil.buildArgs(timestamp, author); - - try (Cursor cursor = getReadableDatabase().query(MessageTable.TABLE_NAME, new String[]{ "1" }, where, whereArgs, null, null, null, "1")) { - return cursor.moveToFirst(); - } - } - - public MessageId getRootOfQuoteChain(@NonNull MessageId id) { - MmsMessageRecord targetMessage; - try { - targetMessage = (MmsMessageRecord) SignalDatabase.messages().getMessageRecord(id.getId()); - } catch (NoSuchMessageException e) { - throw new IllegalArgumentException("Invalid message ID!"); - } - - if (targetMessage.getQuote() == null) { - return id; - } - - String query; - if (targetMessage.getQuote().getAuthor().equals(Recipient.self().getId())) { - query = MessageTable.DATE_SENT + " = " + targetMessage.getQuote().getId() + " AND (" + MessageTable.TYPE + " & " + MessageTypes.BASE_TYPE_MASK + ") = " + MessageTypes.BASE_SENT_TYPE; - } else { - query = MessageTable.DATE_SENT + " = " + targetMessage.getQuote().getId() + " AND " + MessageTable.RECIPIENT_ID + " = '" + targetMessage.getQuote().getAuthor().serialize() + "'"; - } - - try (Reader reader = new Reader(queryTables(PROJECTION, query, null, "1", false))) { - MessageRecord record; - if ((record = reader.getNext()) != null) { - return getRootOfQuoteChain(new MessageId(record.getId())); - } - } - - return id; - } - public List getAllMessagesThatQuote(@NonNull MessageId id) { MessageRecord targetMessage; try { @@ -351,7 +161,7 @@ public class MmsSmsTable extends DatabaseTable { List records = new ArrayList<>(); - try (Reader reader = new Reader(queryTables(PROJECTION, query, order, null, true))) { + try (MessageTable.Reader reader = new MessageTable.MmsReader(queryTables(PROJECTION, query, order, null, true))) { MessageRecord record; while ((record = reader.getNext()) != null) { records.add(record); @@ -380,508 +190,6 @@ public class MmsSmsTable extends DatabaseTable { return " AND " + MessageTable.PARENT_STORY_ID + " = " + parentStoryId; } - public int getUnreadCount(long threadId) { - String selection = MessageTable.READ + " = 0 AND " + MessageTable.STORY_TYPE + " = 0 AND " + MessageTable.THREAD_ID + " = " + threadId + " AND " + MessageTable.PARENT_STORY_ID + " <= 0"; - - try (Cursor cursor = queryTables(PROJECTION, selection, null, null, false)) { - return cursor != null ? cursor.getCount() : 0; - } - } - - public boolean checkMessageExists(@NonNull MessageRecord messageRecord) { - try (Cursor cursor = SignalDatabase.messages().getMessageCursor(messageRecord.getId())) { - return cursor != null && cursor.getCount() > 0; - } - } - - public int getSecureConversationCount(long threadId) { - if (threadId == -1) { - return 0; - } - - return SignalDatabase.messages().getSecureMessageCount(threadId); - } - - public int getOutgoingSecureConversationCount(long threadId) { - if (threadId == -1L) { - return 0; - } - - return SignalDatabase.messages().getOutgoingSecureMessageCount(threadId); - } - - public int getConversationCount(long threadId) { - return SignalDatabase.messages().getMessageCountForThread(threadId); - } - - public int getConversationCount(long threadId, long beforeTime) { - return SignalDatabase.messages().getMessageCountForThread(threadId, beforeTime); - } - - public int getInsecureSentCount(long threadId) { - return SignalDatabase.messages().getInsecureMessagesSentForThread(threadId); - } - - public int getInsecureMessageCountForInsights() { - return SignalDatabase.messages().getInsecureMessageCountForInsights(); - } - - public int getUnexportedInsecureMessagesCount() { - return getUnexportedInsecureMessagesCount(-1); - } - - public int getUnexportedInsecureMessagesCount(long threadId) { - return SignalDatabase.messages().getUnexportedInsecureMessagesCount(threadId); - } - - public int getIncomingMeaningfulMessageCountSince(long threadId, long afterTime) { - return SignalDatabase.messages().getIncomingMeaningfulMessageCountSince(threadId, afterTime); - } - - public int getMessageCountBeforeDate(long date) { - String selection = MessageTable.DATE_RECEIVED + " < " + date; - - try (Cursor cursor = queryTables(new String[] { "COUNT(*)" }, selection, null, null, false)) { - if (cursor != null && cursor.moveToFirst()) { - return cursor.getInt(0); - } - } - - return 0; - } - - public int getSecureMessageCountForInsights() { - return SignalDatabase.messages().getSecureMessageCountForInsights(); - } - - public boolean hasMeaningfulMessage(long threadId) { - if (threadId == -1) { - return false; - } - - return SignalDatabase.messages().hasMeaningfulMessage(threadId); - } - - public long getThreadId(MessageId messageId) { - return SignalDatabase.messages().getThreadIdForMessage(messageId.getId()); - } - - /** - * This is currently only used in an old migration and shouldn't be used by anyone else, just because it flat-out isn't correct. - */ - @Deprecated - public long getThreadForMessageId(long messageId) { - long id = SignalDatabase.messages().getThreadIdForMessage(messageId); - - if (id == -1) return SignalDatabase.messages().getThreadIdForMessage(messageId); - else return id; - } - - public Collection incrementDeliveryReceiptCounts(@NonNull List syncMessageIds, long timestamp) { - return incrementReceiptCounts(syncMessageIds, timestamp, MessageTable.ReceiptType.DELIVERY); - } - - public boolean incrementDeliveryReceiptCount(SyncMessageId syncMessageId, long timestamp) { - return incrementReceiptCount(syncMessageId, timestamp, MessageTable.ReceiptType.DELIVERY); - } - - /** - * @return A list of ID's that were not updated. - */ - public @NonNull Collection incrementReadReceiptCounts(@NonNull List syncMessageIds, long timestamp) { - return incrementReceiptCounts(syncMessageIds, timestamp, MessageTable.ReceiptType.READ); - } - - public boolean incrementReadReceiptCount(SyncMessageId syncMessageId, long timestamp) { - return incrementReceiptCount(syncMessageId, timestamp, MessageTable.ReceiptType.READ); - } - - /** - * @return A list of ID's that were not updated. - */ - public @NonNull Collection incrementViewedReceiptCounts(@NonNull List syncMessageIds, long timestamp) { - return incrementReceiptCounts(syncMessageIds, timestamp, MessageTable.ReceiptType.VIEWED); - } - - public @NonNull Collection incrementViewedNonStoryReceiptCounts(@NonNull List syncMessageIds, long timestamp) { - return incrementReceiptCounts(syncMessageIds, timestamp, MessageTable.ReceiptType.VIEWED, MessageTable.MessageQualifier.NORMAL); - } - - public boolean incrementViewedReceiptCount(SyncMessageId syncMessageId, long timestamp) { - return incrementReceiptCount(syncMessageId, timestamp, MessageTable.ReceiptType.VIEWED); - } - - public @NonNull Collection incrementViewedStoryReceiptCounts(@NonNull List syncMessageIds, long timestamp) { - SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); - Set messageUpdates = new HashSet<>(); - Collection unhandled = new HashSet<>(); - - db.beginTransaction(); - try { - for (SyncMessageId id : syncMessageIds) { - Set updates = incrementReceiptCountInternal(id, timestamp, MessageTable.ReceiptType.VIEWED, MessageTable.MessageQualifier.STORY); - - if (updates.size() > 0) { - messageUpdates.addAll(updates); - } else { - unhandled.add(id); - } - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - - for (MessageUpdate update : messageUpdates) { - ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(update.getMessageId()); - ApplicationDependencies.getDatabaseObserver().notifyVerboseConversationListeners(Collections.singleton(update.getThreadId())); - } - - if (messageUpdates.size() > 0) { - notifyConversationListListeners(); - } - } - - return unhandled; - } - - /** - * Wraps a single receipt update in a transaction and triggers the proper updates. - * - * @return Whether or not some thread was updated. - */ - private boolean incrementReceiptCount(SyncMessageId syncMessageId, long timestamp, @NonNull MessageTable.ReceiptType receiptType) { - return incrementReceiptCount(syncMessageId, timestamp, receiptType, MessageTable.MessageQualifier.ALL); - } - - private boolean incrementReceiptCount(SyncMessageId syncMessageId, long timestamp, @NonNull MessageTable.ReceiptType receiptType, @NonNull MessageTable.MessageQualifier messageQualifier) { - SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); - ThreadTable threadTable = SignalDatabase.threads(); - Set messageUpdates = new HashSet<>(); - - db.beginTransaction(); - try { - messageUpdates = incrementReceiptCountInternal(syncMessageId, timestamp, receiptType, messageQualifier); - - for (MessageUpdate messageUpdate : messageUpdates) { - threadTable.update(messageUpdate.getThreadId(), false); - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - - for (MessageUpdate threadUpdate : messageUpdates) { - ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(threadUpdate.getMessageId()); - } - } - - return messageUpdates.size() > 0; - } - - /** - * Wraps multiple receipt updates in a transaction and triggers the proper updates. - * - * @return All of the messages that didn't result in updates. - */ - private @NonNull Collection incrementReceiptCounts(@NonNull List syncMessageIds, long timestamp, @NonNull MessageTable.ReceiptType receiptType) { - return incrementReceiptCounts(syncMessageIds, timestamp, receiptType, MessageTable.MessageQualifier.ALL); - } - - private @NonNull Collection incrementReceiptCounts(@NonNull List syncMessageIds, long timestamp, @NonNull MessageTable.ReceiptType receiptType, @NonNull MessageTable.MessageQualifier messageQualifier) { - SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); - ThreadTable threadTable = SignalDatabase.threads(); - Set messageUpdates = new HashSet<>(); - Collection unhandled = new HashSet<>(); - - db.beginTransaction(); - try { - for (SyncMessageId id : syncMessageIds) { - Set updates = incrementReceiptCountInternal(id, timestamp, receiptType, messageQualifier); - - if (updates.size() > 0) { - messageUpdates.addAll(updates); - } else { - unhandled.add(id); - } - } - - for (MessageUpdate update : messageUpdates) { - threadTable.updateSilently(update.getThreadId(), false); - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - - for (MessageUpdate update : messageUpdates) { - ApplicationDependencies.getDatabaseObserver().notifyMessageUpdateObservers(update.getMessageId()); - ApplicationDependencies.getDatabaseObserver().notifyVerboseConversationListeners(Collections.singleton(update.getThreadId())); - } - - if (messageUpdates.size() > 0) { - notifyConversationListListeners(); - } - } - - return unhandled; - } - - - /** - * Doesn't do any transactions or updates, so we can re-use the method safely. - */ - private @NonNull Set incrementReceiptCountInternal(SyncMessageId syncMessageId, long timestamp, MessageTable.ReceiptType receiptType, @NonNull MessageTable.MessageQualifier messageQualifier) { - return SignalDatabase.messages().incrementReceiptCount(syncMessageId, timestamp, receiptType, messageQualifier); - } - - public void updateViewedStories(@NonNull Set syncMessageIds) { - SignalDatabase.messages().updateViewedStories(syncMessageIds); - } - - private @NonNull MessageExportState getMessageExportState(@NonNull MessageId messageId) throws NoSuchMessageException { - String table = MessageTable.TABLE_NAME; - String[] projection = SqlUtil.buildArgs(MessageTable.EXPORT_STATE); - String[] args = SqlUtil.buildArgs(messageId.getId()); - - try (Cursor cursor = getReadableDatabase().query(table, projection, ID_WHERE, args, null, null, null, null)) { - if (cursor.moveToFirst()) { - byte[] bytes = CursorUtil.requireBlob(cursor, MessageTable.EXPORT_STATE); - if (bytes == null) { - return MessageExportState.getDefaultInstance(); - } else { - try { - return MessageExportState.parseFrom(bytes); - } catch (InvalidProtocolBufferException e) { - return MessageExportState.getDefaultInstance(); - } - } - } else { - throw new NoSuchMessageException("The requested message does not exist."); - } - } - } - - public void updateMessageExportState(@NonNull MessageId messageId, @NonNull Function transform) throws NoSuchMessageException { - SQLiteDatabase database = getWritableDatabase(); - - database.beginTransaction(); - try { - MessageExportState oldState = getMessageExportState(messageId); - MessageExportState newState = transform.apply(oldState); - - setMessageExportState(messageId, newState); - - database.setTransactionSuccessful(); - } finally { - database.endTransaction(); - } - } - - public void markMessageExported(@NonNull MessageId messageId) { - String table = MessageTable.TABLE_NAME; - ContentValues contentValues = new ContentValues(1); - - contentValues.put(MessageTable.EXPORTED, MessageExportStatus.EXPORTED.getCode()); - - getWritableDatabase().update(table, contentValues, ID_WHERE, SqlUtil.buildArgs(messageId.getId())); - } - - public void markMessageExportFailed(@NonNull MessageId messageId) { - String table = MessageTable.TABLE_NAME; - ContentValues contentValues = new ContentValues(1); - - contentValues.put(MessageTable.EXPORTED, MessageExportStatus.ERROR.getCode()); - - getWritableDatabase().update(table, contentValues, ID_WHERE, SqlUtil.buildArgs(messageId.getId())); - } - - private void setMessageExportState(@NonNull MessageId messageId, @NonNull MessageExportState messageExportState) { - String table = MessageTable.TABLE_NAME; - ContentValues contentValues = new ContentValues(1); - - contentValues.put(MessageTable.EXPORT_STATE, messageExportState.toByteArray()); - - getWritableDatabase().update(table, contentValues, ID_WHERE, SqlUtil.buildArgs(messageId.getId())); - } - - /** - * @return Unhandled ids - */ - public Collection setTimestampReadFromSyncMessage(@NonNull List readMessages, long proposedExpireStarted, @NonNull Map threadToLatestRead) { - SQLiteDatabase db = getWritableDatabase(); - - List> expiringMessages = new LinkedList<>(); - Set updatedThreads = new HashSet<>(); - Collection unhandled = new LinkedList<>(); - - db.beginTransaction(); - try { - for (ReadMessage readMessage : readMessages) { - RecipientId authorId = Recipient.externalPush(readMessage.getSender()).getId(); - TimestampReadResult result = SignalDatabase.messages().setTimestampReadFromSyncMessage(new SyncMessageId(authorId, readMessage.getTimestamp()), - proposedExpireStarted, - threadToLatestRead); - - expiringMessages.addAll(result.expiring); - updatedThreads.addAll(result.threads); - - if (result.threads.isEmpty()) { - unhandled.add(new SyncMessageId(authorId, readMessage.getTimestamp())); - } - } - - for (long threadId : updatedThreads) { - SignalDatabase.threads().updateReadState(threadId); - SignalDatabase.threads().setLastSeen(threadId); - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - for (Pair expiringMessage : expiringMessages) { - ApplicationDependencies.getExpiringMessageManager() - .scheduleDeletion(expiringMessage.first(), true, proposedExpireStarted, expiringMessage.second()); - } - - for (long threadId : updatedThreads) { - notifyConversationListeners(threadId); - } - - return unhandled; - } - - public int getQuotedMessagePosition(long threadId, long quoteId, @NonNull RecipientId recipientId) { - String order = MessageTable.DATE_RECEIVED + " DESC"; - String selection = MessageTable.THREAD_ID + " = " + threadId + " AND " + MessageTable.STORY_TYPE + " = 0" + " AND " + MessageTable.PARENT_STORY_ID + " <= 0"; - - try (Cursor cursor = queryTables(new String[]{ MessageTable.DATE_SENT, MessageTable.RECIPIENT_ID, MessageTable.REMOTE_DELETED}, selection, order, null, false)) { - boolean isOwnNumber = Recipient.resolved(recipientId).isSelf(); - - while (cursor != null && cursor.moveToNext()) { - boolean quoteIdMatches = cursor.getLong(0) == quoteId; - boolean recipientIdMatches = recipientId.equals(RecipientId.from(CursorUtil.requireLong(cursor, MessageTable.RECIPIENT_ID))); - - if (quoteIdMatches && (recipientIdMatches || isOwnNumber)) { - if (CursorUtil.requireBoolean(cursor, MessageTable.REMOTE_DELETED)) { - return -1; - } else { - return cursor.getPosition(); - } - } - } - } - return -1; - } - - public int getMessagePositionInConversation(long threadId, long receivedTimestamp, @NonNull RecipientId recipientId) { - String order = MessageTable.DATE_RECEIVED + " DESC"; - String selection = MessageTable.THREAD_ID + " = " + threadId + " AND " + MessageTable.STORY_TYPE + " = 0" + " AND " + MessageTable.PARENT_STORY_ID + " <= 0"; - - try (Cursor cursor = queryTables(new String[]{ MessageTable.DATE_RECEIVED, MessageTable.RECIPIENT_ID, MessageTable.REMOTE_DELETED}, selection, order, null, false)) { - boolean isOwnNumber = Recipient.resolved(recipientId).isSelf(); - - while (cursor != null && cursor.moveToNext()) { - boolean timestampMatches = cursor.getLong(0) == receivedTimestamp; - boolean recipientIdMatches = recipientId.equals(RecipientId.from(cursor.getLong(1))); - - if (timestampMatches && (recipientIdMatches || isOwnNumber)) { - if (CursorUtil.requireBoolean(cursor, MessageTable.REMOTE_DELETED)) { - return -1; - } else { - return cursor.getPosition(); - } - } - } - } - return -1; - } - - boolean hasReceivedAnyCallsSince(long threadId, long timestamp) { - return SignalDatabase.messages().hasReceivedAnyCallsSince(threadId, timestamp); - } - - - public int getMessagePositionInConversation(long threadId, long receivedTimestamp) { - return getMessagePositionInConversation(threadId, 0, receivedTimestamp); - } - - /** - * Retrieves the position of the message with the provided timestamp in the query results you'd - * get from calling {@link #getConversation(long)}. - * - * Note: This could give back incorrect results in the situation where multiple messages have the - * same received timestamp. However, because this was designed to determine where to scroll to, - * you'll still wind up in about the right spot. - * - * @param groupStoryId Ignored if passed value is <= 0 - */ - public int getMessagePositionInConversation(long threadId, long groupStoryId, long receivedTimestamp) { - final String order; - final String selection; - - if (groupStoryId > 0) { - order = MessageTable.DATE_RECEIVED + " ASC"; - selection = MessageTable.THREAD_ID + " = " + threadId + " AND " + - MessageTable.DATE_RECEIVED + " < " + receivedTimestamp + " AND " + - MessageTable.STORY_TYPE + " = 0 AND " + MessageTable.PARENT_STORY_ID + " = " + groupStoryId; - } else { - order = MessageTable.DATE_RECEIVED + " DESC"; - selection = MessageTable.THREAD_ID + " = " + threadId + " AND " + - MessageTable.DATE_RECEIVED + " > " + receivedTimestamp + " AND " + - MessageTable.STORY_TYPE + " = 0 AND " + MessageTable.PARENT_STORY_ID + " <= 0"; - } - - try (Cursor cursor = queryTables(new String[]{ "COUNT(*)" }, selection, order, null, false)) { - if (cursor != null && cursor.moveToFirst()) { - return cursor.getInt(0); - } - } - return -1; - } - - public long getTimestampForFirstMessageAfterDate(long date) { - String order = MessageTable.DATE_RECEIVED + " ASC"; - String selection = MessageTable.DATE_RECEIVED + " > " + date; - - try (Cursor cursor = queryTables(new String[] { MessageTable.DATE_RECEIVED }, selection, order, "1", false)) { - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(0); - } - } - - return 0; - } - - public void setNotifiedTimestamp(long timestamp, @NonNull List messageIds) { - SignalDatabase.messages().setNotifiedTimestamp(timestamp, messageIds); - } - - public int deleteMessagesInThreadBeforeDate(long threadId, long trimBeforeDate) { - Log.d(TAG, "deleteMessagesInThreadBeforeData(" + threadId + ", " + trimBeforeDate + ")"); - int deletes = SignalDatabase.messages().deleteMessagesInThreadBeforeDate(threadId, trimBeforeDate); - deletes += SignalDatabase.messages().deleteMessagesInThreadBeforeDate(threadId, trimBeforeDate); - return deletes; - } - - public void deleteAbandonedMessages() { - Log.d(TAG, "deleteAbandonedMessages()"); - SignalDatabase.messages().deleteAbandonedMessages(); - SignalDatabase.messages().deleteAbandonedMessages(); - } - - public @NonNull List getReportSpamMessageServerData(long threadId, long timestamp, int limit) { - List data = new ArrayList<>(); - data.addAll(SignalDatabase.messages().getReportSpamMessageServerGuids(threadId, timestamp)); - data.addAll(SignalDatabase.messages().getReportSpamMessageServerGuids(threadId, timestamp)); - return data.stream() - .sorted((l, r) -> -Long.compare(l.getDateReceived(), r.getDateReceived())) - .limit(limit) - .collect(Collectors.toList()); - } - private static @NonNull String buildQuery(String[] projection, String selection, String order, String limit, boolean includeAttachments) { String attachmentJsonJoin; if (includeAttachments) { @@ -942,75 +250,4 @@ public class MmsSmsTable extends DatabaseTable { return databaseHelper.getSignalReadableDatabase().rawQuery(query, null); } - - public static Reader readerFor(@NonNull Cursor cursor) { - return new Reader(cursor); - } - - public static class Reader implements Closeable { - - private final Cursor cursor; - private MessageTable.MmsReader mmsReader; - - public Reader(Cursor cursor) { - this.cursor = cursor; - } - - private MessageTable.MmsReader getMmsReader() { - if (mmsReader == null) { - mmsReader = MessageTable.mmsReaderFor(cursor); - } - - return mmsReader; - } - - public MessageRecord getNext() { - if (cursor == null || !cursor.moveToNext()) - return null; - - return getCurrent(); - } - - public MessageRecord getCurrent() { - return getMmsReader().getCurrent(); - } - - @Override - public void close() { - cursor.close(); - } - } - - static final class TimestampReadResult { - final List> expiring; - final List threads; - - TimestampReadResult(@NonNull List> expiring, @NonNull List threads) { - this.expiring = expiring; - this.threads = threads; - } - } - - private static class QuoteDescriptor { - private final long timestamp; - private final RecipientId author; - - private QuoteDescriptor(long timestamp, RecipientId author) { - this.author = author; - this.timestamp = timestamp; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final QuoteDescriptor that = (QuoteDescriptor) o; - return timestamp == that.timestamp && author.equals(that.author); - } - - @Override - public int hashCode() { - return Objects.hash(author, timestamp); - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt index c5e5365ab3..38bfd70721 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt @@ -296,7 +296,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } val deletes = writableDatabase.withinTransaction { - mmsSms.deleteAbandonedMessages() + messages.deleteAbandonedMessages() attachments.trimAllAbandonedAttachments() groupReceipts.deleteAbandonedRows() mentions.deleteAbandonedMentions() @@ -318,7 +318,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa val deletes = writableDatabase.withinTransaction { trimThreadInternal(threadId, length, trimBeforeDate) - mmsSms.deleteAbandonedMessages() + messages.deleteAbandonedMessages() attachments.trimAllAbandonedAttachments() groupReceipts.deleteAbandonedRows() mentions.deleteAbandonedMentions() @@ -354,7 +354,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa if (finalTrimBeforeDate != NO_TRIM_BEFORE_DATE_SET) { Log.i(TAG, "Trimming thread: $threadId before: $finalTrimBeforeDate") - val deletes = mmsSms.deleteMessagesInThreadBeforeDate(threadId, finalTrimBeforeDate) + val deletes = messages.deleteMessagesInThreadBeforeDate(threadId, finalTrimBeforeDate) if (deletes > 0) { Log.i(TAG, "Trimming deleted $deletes messages thread: $threadId") setLastScrolled(threadId, 0) @@ -389,7 +389,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } fun hasReceivedAnyCallsSince(threadId: Long, timestamp: Long): Boolean { - return mmsSms.hasReceivedAnyCallsSince(threadId, timestamp) + return messages.hasReceivedAnyCallsSince(threadId, timestamp) } fun setEntireThreadRead(threadId: Long): List { @@ -441,7 +441,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa messages.setReactionsSeen(threadId, sinceTimestamp) - val unreadCount = mmsSms.getUnreadCount(threadId) + val unreadCount = messages.getUnreadCount(threadId) val unreadMentionsCount = messages.getUnreadMentionCount(threadId) val contentValues = contentValuesOf( @@ -1180,7 +1180,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa fun updateReadState(threadId: Long) { val previous = getThreadRecord(threadId) - val unreadCount = mmsSms.getUnreadCount(threadId) + val unreadCount = messages.getUnreadCount(threadId) val unreadMentionsCount = messages.getUnreadMentionCount(threadId) writableDatabase @@ -1268,7 +1268,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa if (forcedUnread) { values.put(READ, ReadStatus.FORCED_UNREAD.serialize()) } else if (threadId != null) { - val unreadCount = mmsSms.getUnreadCount(threadId) + val unreadCount = messages.getUnreadCount(threadId) val unreadMentionsCount = messages.getUnreadMentionCount(threadId) values.put(READ, if (unreadCount == 0) ReadStatus.READ.serialize() else ReadStatus.UNREAD.serialize()) @@ -1315,7 +1315,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } private fun update(threadId: Long, unarchive: Boolean, allowDeletion: Boolean, notifyListeners: Boolean): Boolean { - val meaningfulMessages = mmsSms.hasMeaningfulMessage(threadId) + val meaningfulMessages = messages.hasMeaningfulMessage(threadId) val isPinned = getPinnedThreadIds().contains(threadId) val shouldDelete = allowDeletion && !isPinned && !messages.containsStories(threadId) @@ -1331,7 +1331,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } val record: MessageRecord = try { - mmsSms.getConversationSnippet(threadId) + messages.getConversationSnippet(threadId) } catch (e: NoSuchMessageException) { Log.w(TAG, "Failed to get a conversation snippet for thread $threadId") @@ -1400,7 +1400,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } val type: Long = try { - mmsSms.getConversationSnippetType(threadId) + messages.getConversationSnippetType(threadId) } catch (e: NoSuchMessageException) { Log.w(TAG, "Unable to find snippet message for thread $threadId") return @@ -1552,7 +1552,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa Log.w(TAG, "Falling back to unknown message request state for GV2 message") return Extra.forMessageRequest(individualRecipientId) } else { - val recipientId = mmsSms.getGroupAddedBy(record.threadId) + val recipientId = messages.getGroupAddedBy(record.threadId) if (recipientId != null) { return Extra.forGroupMessageRequest(recipientId, individualRecipientId) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/exporter/SignalSmsExportService.kt b/app/src/main/java/org/thoughtcrime/securesms/exporter/SignalSmsExportService.kt index 698a323469..44f4f6bf16 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/exporter/SignalSmsExportService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/exporter/SignalSmsExportService.kt @@ -103,66 +103,66 @@ class SignalSmsExportService : SmsExportService() { } override fun onMessageExportStarted(exportableMessage: ExportableMessage) { - SignalDatabase.mmsSms.updateMessageExportState(exportableMessage.getMessageId()) { + SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) { it.toBuilder().setProgress(MessageExportState.Progress.STARTED).build() } } override fun onMessageExportSucceeded(exportableMessage: ExportableMessage) { - SignalDatabase.mmsSms.updateMessageExportState(exportableMessage.getMessageId()) { + SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) { it.toBuilder().setProgress(MessageExportState.Progress.COMPLETED).build() } - SignalDatabase.mmsSms.markMessageExported(exportableMessage.getMessageId()) + SignalDatabase.messages.markMessageExported(exportableMessage.getMessageId()) } override fun onMessageExportFailed(exportableMessage: ExportableMessage) { - SignalDatabase.mmsSms.updateMessageExportState(exportableMessage.getMessageId()) { + SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) { it.toBuilder().setProgress(MessageExportState.Progress.INIT).build() } - SignalDatabase.mmsSms.markMessageExportFailed(exportableMessage.getMessageId()) + SignalDatabase.messages.markMessageExportFailed(exportableMessage.getMessageId()) } override fun onMessageIdCreated(exportableMessage: ExportableMessage, messageId: Long) { - SignalDatabase.mmsSms.updateMessageExportState(exportableMessage.getMessageId()) { + SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) { it.toBuilder().setMessageId(messageId).build() } } override fun onAttachmentPartExportStarted(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part) { - SignalDatabase.mmsSms.updateMessageExportState(exportableMessage.getMessageId()) { + SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) { it.toBuilder().addStartedAttachments(part.contentId).build() } } override fun onAttachmentPartExportSucceeded(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part) { - SignalDatabase.mmsSms.updateMessageExportState(exportableMessage.getMessageId()) { + SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) { it.toBuilder().addCompletedAttachments(part.contentId).build() } } override fun onAttachmentPartExportFailed(exportableMessage: ExportableMessage, part: ExportableMessage.Mms.Part) { - SignalDatabase.mmsSms.updateMessageExportState(exportableMessage.getMessageId()) { + SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) { val startedAttachments = it.startedAttachmentsList - part.contentId it.toBuilder().clearStartedAttachments().addAllStartedAttachments(startedAttachments).build() } } override fun onRecipientExportStarted(exportableMessage: ExportableMessage, recipient: String) { - SignalDatabase.mmsSms.updateMessageExportState(exportableMessage.getMessageId()) { + SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) { it.toBuilder().addStartedRecipients(recipient).build() } } override fun onRecipientExportSucceeded(exportableMessage: ExportableMessage, recipient: String) { - SignalDatabase.mmsSms.updateMessageExportState(exportableMessage.getMessageId()) { + SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) { it.toBuilder().addCompletedRecipients(recipient).build() } } override fun onRecipientExportFailed(exportableMessage: ExportableMessage, recipient: String) { - SignalDatabase.mmsSms.updateMessageExportState(exportableMessage.getMessageId()) { + SignalDatabase.messages.updateMessageExportState(exportableMessage.getMessageId()) { val startedAttachments = it.startedRecipientsList - recipient it.toBuilder().clearStartedRecipients().addAllStartedRecipients(startedAttachments).build() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java index 6a0b60346c..8eba99fb1b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java @@ -10,6 +10,7 @@ import com.annimon.stream.Stream; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto; +import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.MmsSmsTable; import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.SignalDatabase; @@ -34,9 +35,9 @@ public class InsightsRepository implements InsightsDashboardViewModel.Repository @Override public void getInsightsData(@NonNull Consumer insightsDataConsumer) { SimpleTask.run(() -> { - MmsSmsTable mmsSmsDatabase = SignalDatabase.mmsSms(); - int insecure = mmsSmsDatabase.getInsecureMessageCountForInsights(); - int secure = mmsSmsDatabase.getSecureMessageCountForInsights(); + MessageTable messageTable = SignalDatabase.messages(); + int insecure = messageTable.getInsecureMessageCountForInsights(); + int secure = messageTable.getSecureMessageCountForInsights(); if (insecure + secure == 0) { return new InsightsData(false, 0); diff --git a/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderModel.java b/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderModel.java index b0d9bfe4df..01c72c46a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderModel.java @@ -52,7 +52,7 @@ public final class InviteReminderModel { Long threadId = threadTable.getThreadIdFor(recipient.getId()); if (threadId != null) { - int conversationCount = SignalDatabase.mmsSms().getInsecureSentCount(threadId); + int conversationCount = SignalDatabase.messages().getInsecureMessageSentCount(threadId); if (conversationCount >= SECOND_INVITE_REMINDER_MESSAGE_THRESHOLD && !resolved.hasSeenSecondInviteReminder()) { return new SecondInviteReminderInfo(context, resolved, repository, repository.getPercentOfInsecureMessages(conversationCount)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderRepository.java b/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderRepository.java index 416a50e11e..9acfe62f08 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/invites/InviteReminderRepository.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.invites; import android.content.Context; +import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.MmsSmsTable; import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.SignalDatabase; @@ -29,9 +30,9 @@ public final class InviteReminderRepository implements InviteReminderModel.Repos @Override public int getPercentOfInsecureMessages(int insecureCount) { - MmsSmsTable mmsSmsDatabase = SignalDatabase.mmsSms(); - int insecure = mmsSmsDatabase.getInsecureMessageCountForInsights(); - int secure = mmsSmsDatabase.getSecureMessageCountForInsights(); + MessageTable messageTable = SignalDatabase.messages(); + int insecure = messageTable.getInsecureMessageCountForInsights(); + int secure = messageTable.getSecureMessageCountForInsights(); if (insecure + secure == 0) return 0; return Math.round(100f * (insecureCount / (float) (insecure + secure))); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/migrations/SendReadReceiptsJobMigration.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/migrations/SendReadReceiptsJobMigration.java index be89f29486..685d0d5edf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/migrations/SendReadReceiptsJobMigration.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/migrations/SendReadReceiptsJobMigration.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobmanager.migrations; import androidx.annotation.NonNull; +import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.MmsSmsTable; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.JobMigration; @@ -11,22 +12,22 @@ import java.util.TreeSet; public class SendReadReceiptsJobMigration extends JobMigration { - private final MmsSmsTable mmsSmsDatabase; + private final MessageTable messageTable; - public SendReadReceiptsJobMigration(@NonNull MmsSmsTable mmsSmsDatabase) { + public SendReadReceiptsJobMigration(@NonNull MessageTable messageTable) { super(5); - this.mmsSmsDatabase = mmsSmsDatabase; + this.messageTable = messageTable; } @Override protected @NonNull JobData migrate(@NonNull JobData jobData) { if ("SendReadReceiptJob".equals(jobData.getFactoryKey())) { - return migrateSendReadReceiptJob(mmsSmsDatabase, jobData); + return migrateSendReadReceiptJob(messageTable, jobData); } return jobData; } - private static @NonNull JobData migrateSendReadReceiptJob(@NonNull MmsSmsTable mmsSmsDatabase, @NonNull JobData jobData) { + private static @NonNull JobData migrateSendReadReceiptJob(@NonNull MessageTable messageTable, @NonNull JobData jobData) { Data data = jobData.getData(); if (!data.hasLong("thread")) { @@ -34,7 +35,7 @@ public class SendReadReceiptsJobMigration extends JobMigration { SortedSet threadIds = new TreeSet<>(); for (long id : messageIds) { - long threadForMessageId = mmsSmsDatabase.getThreadForMessageId(id); + long threadForMessageId = messageTable.getThreadIdForMessage(id); if (id != -1) { threadIds.add(threadForMessageId); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/IndividualSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/IndividualSendJob.java index fe82fb9809..cc52c76116 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/IndividualSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/IndividualSendJob.java @@ -156,9 +156,9 @@ public class IndividualSendJob extends PushSendJob { if (recipient.isSelf()) { SyncMessageId id = new SyncMessageId(recipient.getId(), message.getSentTimeMillis()); - SignalDatabase.mmsSms().incrementDeliveryReceiptCount(id, System.currentTimeMillis()); - SignalDatabase.mmsSms().incrementReadReceiptCount(id, System.currentTimeMillis()); - SignalDatabase.mmsSms().incrementViewedReceiptCount(id, System.currentTimeMillis()); + SignalDatabase.messages().incrementDeliveryReceiptCount(id, System.currentTimeMillis()); + SignalDatabase.messages().incrementReadReceiptCount(id, System.currentTimeMillis()); + SignalDatabase.messages().incrementViewedReceiptCount(id, System.currentTimeMillis()); } if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 50e9d68787..a04b2fdbbd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -287,7 +287,7 @@ public final class JobManagerFactories { return Arrays.asList(new RecipientIdJobMigration(application), new RecipientIdFollowUpJobMigration(), new RecipientIdFollowUpJobMigration2(), - new SendReadReceiptsJobMigration(SignalDatabase.mmsSms()), + new SendReadReceiptsJobMigration(SignalDatabase.messages()), new PushProcessMessageQueueJobMigration(application), new RetrieveProfileJobMigration(), new PushDecryptMessageJobEnvelopeMigration(application), diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ReportSpamJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ReportSpamJob.java index 1ae9fe003c..9ed1ac81ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ReportSpamJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ReportSpamJob.java @@ -69,7 +69,7 @@ public class ReportSpamJob extends BaseJob { } int count = 0; - List reportSpamData = SignalDatabase.mmsSms().getReportSpamMessageServerData(threadId, timestamp, MAX_MESSAGE_COUNT); + List reportSpamData = SignalDatabase.messages().getReportSpamMessageServerData(threadId, timestamp, MAX_MESSAGE_COUNT); SignalServiceAccountManager signalServiceAccountManager = ApplicationDependencies.getSignalServiceAccountManager(); for (ReportSpamData data : reportSpamData) { Optional serviceId = Recipient.resolved(data.getRecipientId()).getServiceId(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt index 07eab24491..3ae9230757 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt @@ -95,7 +95,7 @@ class MediaPreviewRepository { stopwatch.split("get message record") val threadId: Long = messageRecord.threadId - val messagePosition: Int = SignalDatabase.mmsSms.getMessagePositionInConversation(threadId, messageRecord.dateReceived) + val messagePosition: Int = SignalDatabase.messages.getMessagePositionInConversation(threadId, messageRecord.dateReceived) stopwatch.split("get message position") val recipientId: RecipientId = SignalDatabase.threads.getRecipientForThreadId(threadId)?.id ?: throw IllegalStateException("Could not find recipient for thread ID $threadId") diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageProcessor.java index 3f32b11f71..335fca665b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageProcessor.java @@ -66,12 +66,10 @@ public class IncomingMessageProcessor { public class Processor implements Closeable { private final Context context; - private final MmsSmsTable mmsSmsDatabase; private final JobManager jobManager; private Processor(@NonNull Context context) { this.context = context; - this.mmsSmsDatabase = SignalDatabase.mmsSms(); this.jobManager = ApplicationDependencies.getJobManager(); } @@ -160,7 +158,7 @@ public class IncomingMessageProcessor { Recipient sender = Recipient.externalPush(envelope.getSourceAddress()); Log.i(TAG, "Received server receipt. Sender: " + sender.getId() + ", Device: " + envelope.getSourceDevice() + ", Timestamp: " + envelope.getTimestamp()); - mmsSmsDatabase.incrementDeliveryReceiptCount(new SyncMessageId(sender.getId(), envelope.getTimestamp()), System.currentTimeMillis()); + SignalDatabase.messages().incrementDeliveryReceiptCount(new SyncMessageId(sender.getId(), envelope.getTimestamp()), System.currentTimeMillis()); SignalDatabase.messageLog().deleteEntryForRecipient(envelope.getTimestamp(), sender.getId(), envelope.getSourceDevice()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index d594b7e3a8..b6e20531cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -1410,7 +1410,7 @@ public final class MessageContentProcessor { Map threadToLatestRead = new HashMap<>(); - Collection unhandled = SignalDatabase.mmsSms().setTimestampReadFromSyncMessage(readMessages, envelopeTimestamp, threadToLatestRead); + Collection unhandled = SignalDatabase.messages().setTimestampReadFromSyncMessage(readMessages, envelopeTimestamp, threadToLatestRead); List markedMessages = SignalDatabase.threads().setReadSince(threadToLatestRead, false); @@ -2111,8 +2111,8 @@ public final class MessageContentProcessor { if (recipient.isSelf()) { SyncMessageId id = new SyncMessageId(recipient.getId(), message.getTimestamp()); - SignalDatabase.mmsSms().incrementDeliveryReceiptCount(id, System.currentTimeMillis()); - SignalDatabase.mmsSms().incrementReadReceiptCount(id, System.currentTimeMillis()); + SignalDatabase.messages().incrementDeliveryReceiptCount(id, System.currentTimeMillis()); + SignalDatabase.messages().incrementReadReceiptCount(id, System.currentTimeMillis()); } database.setTransactionSuccessful(); @@ -2224,8 +2224,8 @@ public final class MessageContentProcessor { if (recipient.isSelf()) { SyncMessageId id = new SyncMessageId(recipient.getId(), message.getTimestamp()); - SignalDatabase.mmsSms().incrementDeliveryReceiptCount(id, System.currentTimeMillis()); - SignalDatabase.mmsSms().incrementReadReceiptCount(id, System.currentTimeMillis()); + SignalDatabase.messages().incrementDeliveryReceiptCount(id, System.currentTimeMillis()); + SignalDatabase.messages().incrementReadReceiptCount(id, System.currentTimeMillis()); } database.setTransactionSuccessful(); @@ -2333,8 +2333,8 @@ public final class MessageContentProcessor { if (recipients.isSelf()) { SyncMessageId id = new SyncMessageId(recipients.getId(), message.getTimestamp()); - SignalDatabase.mmsSms().incrementDeliveryReceiptCount(id, System.currentTimeMillis()); - SignalDatabase.mmsSms().incrementReadReceiptCount(id, System.currentTimeMillis()); + SignalDatabase.messages().incrementDeliveryReceiptCount(id, System.currentTimeMillis()); + SignalDatabase.messages().incrementReadReceiptCount(id, System.currentTimeMillis()); } database.setTransactionSuccessful(); @@ -2502,8 +2502,8 @@ public final class MessageContentProcessor { if (recipient.isSelf()) { SyncMessageId id = new SyncMessageId(recipient.getId(), message.getTimestamp()); - SignalDatabase.mmsSms().incrementDeliveryReceiptCount(id, System.currentTimeMillis()); - SignalDatabase.mmsSms().incrementReadReceiptCount(id, System.currentTimeMillis()); + SignalDatabase.messages().incrementDeliveryReceiptCount(id, System.currentTimeMillis()); + SignalDatabase.messages().incrementReadReceiptCount(id, System.currentTimeMillis()); } return threadId; @@ -2659,17 +2659,17 @@ public final class MessageContentProcessor { final Collection unhandled; if (readReceipts && storyViewedReceipts) { - unhandled = SignalDatabase.mmsSms().incrementViewedReceiptCounts(ids, content.getTimestamp()); + unhandled = SignalDatabase.messages().incrementViewedReceiptCounts(ids, content.getTimestamp()); } else if (readReceipts) { - unhandled = SignalDatabase.mmsSms().incrementViewedNonStoryReceiptCounts(ids, content.getTimestamp()); + unhandled = SignalDatabase.messages().incrementViewedNonStoryReceiptCounts(ids, content.getTimestamp()); } else { - unhandled = SignalDatabase.mmsSms().incrementViewedStoryReceiptCounts(ids, content.getTimestamp()); + unhandled = SignalDatabase.messages().incrementViewedStoryReceiptCounts(ids, content.getTimestamp()); } Set handled = new HashSet<>(ids); handled.removeAll(unhandled); - SignalDatabase.mmsSms().updateViewedStories(handled); + SignalDatabase.messages().updateViewedStories(handled); if (unhandled.size() > 0) { RecipientId selfId = Recipient.self().getId(); @@ -2698,7 +2698,7 @@ public final class MessageContentProcessor { .map(t -> new SyncMessageId(senderRecipient.getId(), t)) .toList(); - Collection unhandled = SignalDatabase.mmsSms().incrementDeliveryReceiptCounts(ids, System.currentTimeMillis()); + Collection unhandled = SignalDatabase.messages().incrementDeliveryReceiptCounts(ids, System.currentTimeMillis()); for (SyncMessageId id : unhandled) { warn(String.valueOf(content.getTimestamp()), "[handleDeliveryReceipt] Could not find matching message! timestamp: " + id.getTimetamp() + " author: " + id.getRecipientId()); @@ -2729,7 +2729,7 @@ public final class MessageContentProcessor { .map(t -> new SyncMessageId(senderRecipient.getId(), t)) .toList(); - Collection unhandled = SignalDatabase.mmsSms().incrementReadReceiptCounts(ids, content.getTimestamp()); + Collection unhandled = SignalDatabase.messages().incrementReadReceiptCounts(ids, content.getTimestamp()); if (unhandled.size() > 0) { RecipientId selfId = Recipient.self().getId(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt index b274f70c18..6f9a232119 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt @@ -212,7 +212,7 @@ class DefaultMessageNotifier(context: Application) : MessageNotifier { updateBadge(context, state.messageCount) val messageIds: List = state.notificationItems.map { it.id } - SignalDatabase.mmsSms.setNotifiedTimestamp(System.currentTimeMillis(), messageIds) + SignalDatabase.messages.setNotifiedTimestamp(System.currentTimeMillis(), messageIds) Log.i(TAG, "threads: ${state.threadCount} messages: ${state.messageCount}") diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItem.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItem.kt index 2e2c329424..a14ca4b575 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItem.kt @@ -215,7 +215,7 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : N override fun getStartingPosition(context: Context): Int { return if (thread.groupStoryId != null) { - SignalDatabase.mmsSms.getMessagePositionInConversation(thread.threadId, thread.groupStoryId, record.dateReceived) + SignalDatabase.messages.getMessagePositionInConversation(thread.threadId, thread.groupStoryId, record.dateReceived) } else { -1 } @@ -328,7 +328,7 @@ class ReactionNotification(threadRecipient: Recipient, record: MessageRecord, va } override fun getStartingPosition(context: Context): Int { - return SignalDatabase.mmsSms.getMessagePositionInConversation(thread.threadId, thread.groupStoryId ?: 0L, record.dateReceived) + return SignalDatabase.messages.getMessagePositionInConversation(thread.threadId, thread.groupStoryId ?: 0L, record.dateReceived) } override fun getLargeIconUri(): Uri? = null diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt index 4d1b5cb127..143bbb4743 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt @@ -4,7 +4,6 @@ import androidx.annotation.WorkerThread import org.signal.core.util.CursorUtil import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.database.MessageTable -import org.thoughtcrime.securesms.database.MmsSmsTable import org.thoughtcrime.securesms.database.NoSuchMessageException import org.thoughtcrime.securesms.database.RecipientTable import org.thoughtcrime.securesms.database.SignalDatabase @@ -31,7 +30,7 @@ object NotificationStateProvider { return NotificationState.EMPTY } - MmsSmsTable.readerFor(unreadMessages).use { reader -> + MessageTable.mmsReaderFor(unreadMessages).use { reader -> var record: MessageRecord? = reader.next while (record != null) { val threadRecipient: Recipient? = SignalDatabase.threads.getRecipientForThreadId(record.threadId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java index 0198ccebd2..043be58f00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java @@ -257,7 +257,7 @@ public class RecipientUtil { @WorkerThread public static boolean isPreMessageRequestThread(@Nullable Long threadId) { long beforeTime = SignalStore.misc().getMessageRequestEnableTime(); - return threadId != null && SignalDatabase.mmsSms().getConversationCount(threadId, beforeTime) > 0; + return threadId != null && SignalDatabase.messages().getMessageCountForThread(threadId, beforeTime) > 0; } @WorkerThread @@ -272,7 +272,7 @@ public class RecipientUtil { return; } - boolean firstMessage = SignalDatabase.mmsSms().getOutgoingSecureConversationCount(threadId) == 0; + boolean firstMessage = SignalDatabase.messages().getOutgoingSecureMessageCount(threadId) == 0; if (firstMessage) { SignalDatabase.recipients().setProfileSharing(recipient.getId(), true); @@ -317,7 +317,7 @@ public class RecipientUtil { return false; } - if (threadId == -1 || !SignalDatabase.mmsSms().hasMeaningfulMessage(threadId)) { + if (threadId == -1 || !SignalDatabase.messages().hasMeaningfulMessage(threadId)) { SignalDatabase.recipients().setExpireMessages(recipient.getId(), defaultTimer); OutgoingMessage outgoingMessage = OutgoingMessage.expirationUpdateMessage(recipient, System.currentTimeMillis(), defaultTimer * 1000L); MessageSender.send(context, outgoingMessage, SignalDatabase.threads().getOrCreateThreadIdFor(recipient), false, null, null); @@ -349,7 +349,7 @@ public class RecipientUtil { @WorkerThread public static boolean hasSentMessageInThread(@Nullable Long threadId) { - return threadId != null && SignalDatabase.mmsSms().getOutgoingSecureConversationCount(threadId) != 0; + return threadId != null && SignalDatabase.messages().getOutgoingSecureMessageCount(threadId) != 0; } public static boolean isSmsOnly(long threadId, @NonNull Recipient threadRecipient) { @@ -363,7 +363,7 @@ public class RecipientUtil { return true; } - return SignalDatabase.mmsSms().getSecureConversationCount(threadId) == 0 && + return SignalDatabase.messages().getSecureMessageCount(threadId) == 0 && !SignalDatabase.threads().hasReceivedAnyCallsSince(threadId, 0); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/TrimThreadsByDateManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/TrimThreadsByDateManager.java index e5cf2ea726..73c49ef2a8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/TrimThreadsByDateManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/TrimThreadsByDateManager.java @@ -9,7 +9,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.database.MmsSmsTable; +import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.ThreadTable; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -20,14 +20,14 @@ public class TrimThreadsByDateManager extends TimedEventManager 0) { + if (messageTable.getMessageCountBeforeDate(trimBeforeDate) > 0) { Log.i(TAG, "Messages exist before date, trim immediately"); return new TrimEvent(0); } - long timestamp = mmsSmsDatabase.getTimestampForFirstMessageAfterDate(trimBeforeDate); + long timestamp = messageTable.getTimestampForFirstMessageAfterDate(trimBeforeDate); if (timestamp == 0) { return null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java b/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java index 3ac2733bc7..ff0d880d12 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sms/MessageSender.java @@ -598,10 +598,9 @@ public class MessageSender { private static void sendLocalMediaSelf(Context context, long messageId) { try { ExpiringMessageManager expirationManager = ApplicationDependencies.getExpiringMessageManager(); - MessageTable mmsDatabase = SignalDatabase.messages(); - MmsSmsTable mmsSmsDatabase = SignalDatabase.mmsSms(); - OutgoingMessage message = mmsDatabase.getOutgoingMessage(messageId); - SyncMessageId syncId = new SyncMessageId(Recipient.self().getId(), message.getSentTimeMillis()); + MessageTable mmsDatabase = SignalDatabase.messages(); + OutgoingMessage message = mmsDatabase.getOutgoingMessage(messageId); + SyncMessageId syncId = new SyncMessageId(Recipient.self().getId(), message.getSentTimeMillis()); List attachments = new LinkedList<>(); @@ -633,9 +632,9 @@ public class MessageSender { mmsDatabase.markAsSent(messageId, true); mmsDatabase.markUnidentified(messageId, true); - mmsSmsDatabase.incrementDeliveryReceiptCount(syncId, System.currentTimeMillis()); - mmsSmsDatabase.incrementReadReceiptCount(syncId, System.currentTimeMillis()); - mmsSmsDatabase.incrementViewedReceiptCount(syncId, System.currentTimeMillis()); + mmsDatabase.incrementDeliveryReceiptCount(syncId, System.currentTimeMillis()); + mmsDatabase.incrementReadReceiptCount(syncId, System.currentTimeMillis()); + mmsDatabase.incrementViewedReceiptCount(syncId, System.currentTimeMillis()); if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) { mmsDatabase.markExpireStarted(messageId); diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/MmsSmsDatabaseTest.kt b/app/src/test/java/org/thoughtcrime/securesms/database/MmsSmsDatabaseTest.kt index ab547027e9..2f184748e9 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/database/MmsSmsDatabaseTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/database/MmsSmsDatabaseTest.kt @@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.testing.TestDatabaseUtil class MmsSmsDatabaseTest { private lateinit var mmsSmsTable: MmsSmsTable + private lateinit var messageTable: MessageTable private lateinit var db: SQLiteDatabase @Before @@ -28,6 +29,7 @@ class MmsSmsDatabaseTest { db = sqlCipher.writableDatabase mmsSmsTable = MmsSmsTable(ApplicationProvider.getApplicationContext(), sqlCipher) + messageTable = MessageTable(ApplicationProvider.getApplicationContext(), sqlCipher) } @After @@ -38,7 +40,7 @@ class MmsSmsDatabaseTest { @Test fun `getConversationSnippet when single normal SMS, return SMS message id and transport as false`() { TestSms.insert(db) - mmsSmsTable.getConversationSnippetCursor(1).use { cursor -> + messageTable.getConversationSnippetCursor(1).use { cursor -> cursor.moveToFirst() assertEquals(1, CursorUtil.requireLong(cursor, MessageTable.ID)) } @@ -47,7 +49,7 @@ class MmsSmsDatabaseTest { @Test fun `getConversationSnippet when single normal MMS, return MMS message id and transport as true`() { TestMms.insert(db) - mmsSmsTable.getConversationSnippetCursor(1).use { cursor -> + messageTable.getConversationSnippetCursor(1).use { cursor -> cursor.moveToFirst() assertEquals(1, CursorUtil.requireLong(cursor, MessageTable.ID)) } @@ -58,13 +60,13 @@ class MmsSmsDatabaseTest { val timestamp = System.currentTimeMillis() TestMms.insert(db, receivedTimestampMillis = timestamp + 2) - mmsSmsTable.getConversationSnippetCursor(1).use { cursor -> + messageTable.getConversationSnippetCursor(1).use { cursor -> cursor.moveToFirst() assertEquals(1, CursorUtil.requireLong(cursor, MessageTable.ID)) } TestSms.insert(db, receivedTimestampMillis = timestamp + 3, type = MessageTypes.BASE_SENDING_TYPE or MessageTypes.SECURE_MESSAGE_BIT or MessageTypes.PUSH_MESSAGE_BIT or MessageTypes.GROUP_V2_LEAVE_BITS) - mmsSmsTable.getConversationSnippetCursor(1).use { cursor -> + messageTable.getConversationSnippetCursor(1).use { cursor -> cursor.moveToFirst() assertEquals(1, CursorUtil.requireLong(cursor, MessageTable.ID)) } diff --git a/app/src/test/java/org/thoughtcrime/securesms/jobmanager/migrations/SendReadReceiptsJobMigrationTest.java b/app/src/test/java/org/thoughtcrime/securesms/jobmanager/migrations/SendReadReceiptsJobMigrationTest.java index ee24ddbe42..9609d7825f 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/jobmanager/migrations/SendReadReceiptsJobMigrationTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/jobmanager/migrations/SendReadReceiptsJobMigrationTest.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.jobmanager.migrations; import org.junit.Test; +import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.MmsSmsTable; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.JobMigration; @@ -17,7 +18,7 @@ import static org.mockito.Mockito.when; public class SendReadReceiptsJobMigrationTest { - private final MmsSmsTable mockDatabase = mock(MmsSmsTable.class); + private final MessageTable mockDatabase = mock(MessageTable.class); private final SendReadReceiptsJobMigration testSubject = new SendReadReceiptsJobMigration(mockDatabase); @Test @@ -30,7 +31,7 @@ public class SendReadReceiptsJobMigrationTest { .putString("recipient", RecipientId.from(2).serialize()) .putLongArray("message_ids", new long[]{1, 2, 3, 4, 5}) .putLong("timestamp", 292837649).build()); - when(mockDatabase.getThreadForMessageId(anyLong())).thenReturn(1234L); + when(mockDatabase.getThreadIdForMessage(anyLong())).thenReturn(1234L); // WHEN JobMigration.JobData result = testSubject.migrate(jobData); @@ -52,7 +53,7 @@ public class SendReadReceiptsJobMigrationTest { .putString("recipient", RecipientId.from(2).serialize()) .putLongArray("message_ids", new long[]{}) .putLong("timestamp", 292837649).build()); - when(mockDatabase.getThreadForMessageId(anyLong())).thenReturn(-1L); + when(mockDatabase.getThreadIdForMessage(anyLong())).thenReturn(-1L); // WHEN JobMigration.JobData result = testSubject.migrate(jobData); diff --git a/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientUtilTest.java b/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientUtilTest.java index fd19272734..2a603bc0aa 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientUtilTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientUtilTest.java @@ -10,11 +10,10 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -import org.thoughtcrime.securesms.database.MmsSmsTable; +import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.ThreadTable; -import org.thoughtcrime.securesms.util.FeatureFlags; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -29,22 +28,19 @@ public class RecipientUtilTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); - private Context context = mock(Context.class); + private Context context = mock(Context.class); private Recipient recipient = mock(Recipient.class); private ThreadTable mockThreadTable = mock(ThreadTable.class); - private MmsSmsTable mockMmsSmsDatabase = mock(MmsSmsTable.class); + private MessageTable mockMessageTable = mock(MessageTable.class); private RecipientTable mockRecipientTable = mock(RecipientTable.class); @Mock private MockedStatic signalDatabaseMockedStatic; - @Mock - private MockedStatic featureFlagsMockedStatic; - @Before public void setUp() { signalDatabaseMockedStatic.when(SignalDatabase::threads).thenReturn(mockThreadTable); - signalDatabaseMockedStatic.when(SignalDatabase::mmsSms).thenReturn(mockMmsSmsDatabase); + signalDatabaseMockedStatic.when(SignalDatabase::messages).thenReturn(mockMessageTable); signalDatabaseMockedStatic.when(SignalDatabase::recipients).thenReturn(mockRecipientTable); when(recipient.getId()).thenReturn(RecipientId.from(5)); @@ -73,7 +69,7 @@ public class RecipientUtilTest { public void givenIHaveSentASecureMessageInThisThread_whenIsThreadMessageRequestAccepted_thenIExpectTrue() { // GIVEN when(mockThreadTable.getRecipientForThreadId(anyLong())).thenReturn(recipient); - when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(1L)).thenReturn(5); + when(mockMessageTable.getOutgoingSecureMessageCount(1L)).thenReturn(5); // WHEN boolean result = RecipientUtil.isMessageRequestAccepted(context, 1L); @@ -87,7 +83,7 @@ public class RecipientUtilTest { // GIVEN when(recipient.isProfileSharing()).thenReturn(true); when(mockThreadTable.getRecipientForThreadId(anyLong())).thenReturn(recipient); - when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(1L)).thenReturn(0); + when(mockMessageTable.getOutgoingSecureMessageCount(1L)).thenReturn(0); // WHEN boolean result = RecipientUtil.isMessageRequestAccepted(context, 1L); @@ -101,7 +97,7 @@ public class RecipientUtilTest { // GIVEN when(recipient.isSystemContact()).thenReturn(true); when(mockThreadTable.getRecipientForThreadId(anyLong())).thenReturn(recipient); - when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(1L)).thenReturn(0); + when(mockMessageTable.getOutgoingSecureMessageCount(1L)).thenReturn(0); // WHEN boolean result = RecipientUtil.isMessageRequestAccepted(context, 1L); @@ -115,8 +111,8 @@ public class RecipientUtilTest { public void givenIHaveReceivedASecureMessageIHaveNotSentASecureMessageAndRecipientIsNotSystemContactAndNotProfileSharing_whenIsThreadMessageRequestAccepted_thenIExpectFalse() { // GIVEN when(mockThreadTable.getRecipientForThreadId(anyLong())).thenReturn(recipient); - when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(1L)).thenReturn(0); - when(mockMmsSmsDatabase.getSecureConversationCount(1L)).thenReturn(5); + when(mockMessageTable.getOutgoingSecureMessageCount(1L)).thenReturn(0); + when(mockMessageTable.getSecureMessageCount(1L)).thenReturn(5); // WHEN boolean result = RecipientUtil.isMessageRequestAccepted(context, 1L); @@ -129,8 +125,8 @@ public class RecipientUtilTest { public void givenIHaveNotReceivedASecureMessageIHaveNotSentASecureMessageAndRecipientIsNotSystemContactAndNotProfileSharing_whenIsThreadMessageRequestAccepted_thenIExpectTrue() { // GIVEN when(mockThreadTable.getRecipientForThreadId(anyLong())).thenReturn(recipient); - when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(1L)).thenReturn(0); - when(mockMmsSmsDatabase.getSecureConversationCount(1L)).thenReturn(0); + when(mockMessageTable.getOutgoingSecureMessageCount(1L)).thenReturn(0); + when(mockMessageTable.getSecureMessageCount(1L)).thenReturn(0); // WHEN boolean result = RecipientUtil.isMessageRequestAccepted(context, 1L); @@ -151,7 +147,7 @@ public class RecipientUtilTest { @Test public void givenNonZeroOutgoingSecureMessageCount_whenIsRecipientMessageRequestAccepted_thenIExpectTrue() { // GIVEN - when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(anyLong())).thenReturn(1); + when(mockMessageTable.getOutgoingSecureMessageCount(anyLong())).thenReturn(1); // WHEN boolean result = RecipientUtil.isMessageRequestAccepted(context, recipient); @@ -189,7 +185,7 @@ public class RecipientUtilTest { public void givenNoSecureMessagesSentSomeSecureMessagesReceivedNotSharingAndNotSystemContact_whenIsRecipientMessageRequestAccepted_thenIExpectFalse() { // GIVEN when(recipient.isRegistered()).thenReturn(true); - when(mockMmsSmsDatabase.getSecureConversationCount(anyLong())).thenReturn(5); + when(mockMessageTable.getSecureMessageCount(anyLong())).thenReturn(5); // WHEN boolean result = RecipientUtil.isMessageRequestAccepted(context, recipient); @@ -201,7 +197,7 @@ public class RecipientUtilTest { @Test public void givenNoSecureMessagesSentNoSecureMessagesReceivedNotSharingAndNotSystemContact_whenIsRecipientMessageRequestAccepted_thenIExpectTrue() { // GIVEN - when(mockMmsSmsDatabase.getSecureConversationCount(anyLong())).thenReturn(0); + when(mockMessageTable.getSecureMessageCount(anyLong())).thenReturn(0); // WHEN boolean result = RecipientUtil.isMessageRequestAccepted(context, recipient); @@ -214,7 +210,7 @@ public class RecipientUtilTest { @Test public void givenNoSecureMessagesSent_whenIShareProfileIfFirstSecureMessage_thenIShareProfile() { // GIVEN - when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(anyLong())).thenReturn(0); + when(mockMessageTable.getOutgoingSecureMessageCount(anyLong())).thenReturn(0); // WHEN RecipientUtil.shareProfileIfFirstSecureMessage(recipient); @@ -227,7 +223,7 @@ public class RecipientUtilTest { @Test public void givenSecureMessagesSent_whenIShareProfileIfFirstSecureMessage_thenIShareProfile() { // GIVEN - when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(anyLong())).thenReturn(5); + when(mockMessageTable.getOutgoingSecureMessageCount(anyLong())).thenReturn(5); // WHEN RecipientUtil.shareProfileIfFirstSecureMessage(recipient);