diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationListItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationListItem.java index 29a509f5fd..8d7ae0137c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationListItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationListItem.java @@ -6,7 +6,7 @@ import androidx.lifecycle.LifecycleOwner; import com.bumptech.glide.RequestManager; import org.thoughtcrime.securesms.conversationlist.model.ConversationSet; -import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient; import java.util.Locale; import java.util.Set; @@ -14,7 +14,7 @@ import java.util.Set; public interface BindableConversationListItem extends Unbindable { void bind(@NonNull LifecycleOwner lifecycleOwner, - @NonNull ThreadRecord thread, + @NonNull ThreadWithRecipient thread, @NonNull RequestManager requestManager, @NonNull Locale locale, @NonNull Set typingThreads, @NonNull ConversationSet selectedConversations, diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatArchiveExporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatArchiveExporter.kt index 989fefcaf6..10621f7e47 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatArchiveExporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatArchiveExporter.kt @@ -61,7 +61,7 @@ class ChatArchiveExporter(private val cursor: Cursor, private val db: SignalData expirationTimerMs = cursor.requireLong(RecipientTable.MESSAGE_EXPIRATION_TIME).seconds.inWholeMilliseconds.takeIf { it > 0 }, expireTimerVersion = cursor.requireInt(RecipientTable.MESSAGE_EXPIRATION_TIME_VERSION), muteUntilMs = cursor.requireLong(RecipientTable.MUTE_UNTIL).takeIf { it > 0 }, - markedUnread = ThreadTable.ReadStatus.deserialize(cursor.requireInt(ThreadTable.READ)) == ThreadTable.ReadStatus.FORCED_UNREAD, + markedUnread = ThreadTable.ReadStatus.deserialize(cursor.requireInt(ThreadTable.READ)) == ThreadTable.ReadStatus.ForcedUnread, dontNotifyForMentionsIfMuted = RecipientTable.NotificationSetting.DO_NOT_NOTIFY.id == cursor.requireInt(RecipientTable.MENTION_SETTING), style = ChatStyleConverter.constructRemoteChatStyle( db = db, diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatArchiveImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatArchiveImporter.kt index 5d4ef14b9e..0a6ca9262b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatArchiveImporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatArchiveImporter.kt @@ -46,7 +46,7 @@ object ChatArchiveImporter { ThreadTable.RECIPIENT_ID to recipientId.serialize(), ThreadTable.PINNED_ORDER to chat.pinnedOrder, ThreadTable.ARCHIVED to chat.archived.toInt(), - ThreadTable.READ to if (chat.markedUnread) ThreadTable.ReadStatus.FORCED_UNREAD.serialize() else ThreadTable.ReadStatus.READ.serialize(), + ThreadTable.READ to if (chat.markedUnread) ThreadTable.ReadStatus.ForcedUnread.serialize() else ThreadTable.ReadStatus.Read.serialize(), ThreadTable.ACTIVE to 1 ) .run() diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/dataseeding/DataSeedingPlaygroundFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/dataseeding/DataSeedingPlaygroundFragment.kt index 54825f4b30..e1a7905b60 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/dataseeding/DataSeedingPlaygroundFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/dataseeding/DataSeedingPlaygroundFragment.kt @@ -54,7 +54,7 @@ import org.signal.core.ui.compose.DayNightPreviews import org.signal.core.ui.compose.Previews import org.signal.core.ui.compose.Rows import org.signal.core.ui.compose.SignalIcons -import org.thoughtcrime.securesms.database.model.ThreadRecord +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient class DataSeedingPlaygroundFragment : ComposeFragment() { @@ -285,7 +285,7 @@ fun Screen( @Composable private fun ThreadSelectionRow( - thread: ThreadRecord, + thread: ThreadWithRecipient, isSelected: Boolean, onSelectionChanged: (Boolean) -> Unit ) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/dataseeding/DataSeedingPlaygroundViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/dataseeding/DataSeedingPlaygroundViewModel.kt index 5448be0694..4b86f8dc16 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/dataseeding/DataSeedingPlaygroundViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/dataseeding/DataSeedingPlaygroundViewModel.kt @@ -25,7 +25,7 @@ import org.thoughtcrime.securesms.attachments.Attachment import org.thoughtcrime.securesms.attachments.UriAttachment import org.thoughtcrime.securesms.database.AttachmentTable import org.thoughtcrime.securesms.database.SignalDatabase -import org.thoughtcrime.securesms.database.model.ThreadRecord +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient import org.thoughtcrime.securesms.mms.OutgoingMessage import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.util.MediaUtil @@ -43,7 +43,7 @@ class DataSeedingPlaygroundViewModel(application: Application) : AndroidViewMode fun loadThreads() { viewModelScope.launch(Dispatchers.IO) { try { - val threads = mutableListOf() + val threads = mutableListOf() val cursor: Cursor = SignalDatabase.threads.getRecentConversationList( limit = MAX_RECENT_THREADS, includeInactiveGroups = false, @@ -213,7 +213,7 @@ class DataSeedingPlaygroundViewModel(application: Application) : AndroidViewMode } data class DataSeedingPlaygroundState( - val threads: List = emptyList(), + val threads: List = emptyList(), val selectedThreads: Set = emptySet(), val mediaFiles: List = emptyList(), val selectedFolderPath: String = "" diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchData.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchData.kt index 468270574b..5d94ba338a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchData.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchData.kt @@ -5,7 +5,7 @@ import androidx.annotation.VisibleForTesting import org.thoughtcrime.securesms.contacts.HeaderAction import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode import org.thoughtcrime.securesms.database.model.GroupRecord -import org.thoughtcrime.securesms.database.model.ThreadRecord +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient import org.thoughtcrime.securesms.groups.GroupsInCommonSummary import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.search.MessageResult @@ -49,8 +49,8 @@ sealed class ContactSearchData(val contactSearchKey: ContactSearchKey) { */ data class Thread( val query: String, - val threadRecord: ThreadRecord - ) : ContactSearchData(ContactSearchKey.Thread(threadRecord.threadId)) + val threadWithRecipient: ThreadWithRecipient + ) : ContactSearchData(ContactSearchKey.Thread(threadWithRecipient.threadId)) /** * A row displaying a group which has members that match the given query. diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchPagedDataSource.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchPagedDataSource.kt index 7247e939d9..848cf4ef6f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchPagedDataSource.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchPagedDataSource.kt @@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.database.GroupTable import org.thoughtcrime.securesms.database.RecipientTable import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode import org.thoughtcrime.securesms.database.model.GroupRecord -import org.thoughtcrime.securesms.database.model.ThreadRecord +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient import org.thoughtcrime.securesms.keyvalue.StorySend import org.thoughtcrime.securesms.phonenumbers.NumberUtil import org.thoughtcrime.securesms.recipients.Recipient @@ -476,7 +476,7 @@ class ContactSearchPagedDataSource( } } - private fun getThreadData(query: String?, unreadOnly: Boolean): ContactSearchIterator { + private fun getThreadData(query: String?, unreadOnly: Boolean): ContactSearchIterator { check(searchRepository != null) if (searchCache.threadSearchResult == null && query != null) { searchCache = searchCache.copy(threadSearchResult = searchRepository.queryThreadsSync(query, unreadOnly)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListDataSource.java index 93b917084c..01c2f6031c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListDataSource.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListDataSource.java @@ -19,7 +19,7 @@ import org.thoughtcrime.securesms.database.MessageTypes; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.ThreadTable; import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient; import org.thoughtcrime.securesms.database.model.UpdateDescription; import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.recipients.Recipient; @@ -79,7 +79,7 @@ abstract class ConversationListDataSource implements PagedDataSource needsResolve = new HashSet<>(); try (ConversationReader reader = new ConversationReader(getCursor(start, length))) { - ThreadRecord record; + ThreadWithRecipient record; while ((record = reader.getNext()) != null && !cancellationSignal.isCanceled()) { conversations.add(new Conversation(record)); recipients.add(record.getRecipient()); 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 7bda5541df..824779b65a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -135,7 +135,7 @@ import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter; import org.thoughtcrime.securesms.database.MessageTable.MarkedMessageInfo; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.ThreadTable; -import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient; import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.groups.SelectionLimits; import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob; @@ -668,7 +668,7 @@ public class ConversationListFragment extends MainFragment implements Conversati } } - private void onConversationClicked(@NonNull ThreadRecord threadRecord) { + private void onConversationClicked(@NonNull ThreadWithRecipient threadRecord) { hideKeyboard(); getNavigator().goToConversation(threadRecord.getRecipient().getId(), threadRecord.getThreadId(), @@ -1843,7 +1843,7 @@ public class ConversationListFragment extends MainFragment implements Conversati } private void onTrueSwipe(RecyclerView.ViewHolder viewHolder) { - ThreadRecord thread = ((ConversationListItem) viewHolder.itemView).getThread(); + ThreadWithRecipient thread = ((ConversationListItem) viewHolder.itemView).getThread(); onItemSwiped(thread.getThreadId(), thread.getUnreadCount(), thread.getUnreadSelfMentionsCount()); } @@ -1966,12 +1966,12 @@ public class ConversationListFragment extends MainFragment implements Conversati @Override public void onThreadClicked(@NonNull View view, @NonNull ContactSearchData.Thread thread, boolean isSelected) { - onConversationClicked(thread.getThreadRecord()); + onConversationClicked(thread.getThreadWithRecipient()); } @Override public boolean onThreadLongClicked(@NonNull View view, @NonNull ContactSearchData.Thread thread) { - return showConversationContextMenu(new Conversation(thread.getThreadRecord()), view, true); + return showConversationContextMenu(new Conversation(thread.getThreadWithRecipient()), view, true); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java index 93cf31dcf5..5f8647ae6b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java @@ -70,7 +70,7 @@ import org.thoughtcrime.securesms.database.MessageTypes; import org.thoughtcrime.securesms.database.ThreadTable; import org.thoughtcrime.securesms.database.model.LiveUpdateMessage; import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient; import org.thoughtcrime.securesms.database.model.UpdateDescription; import org.thoughtcrime.securesms.fonts.SignalSymbols.Glyph; import org.thoughtcrime.securesms.glide.targets.GlideLiveDataTarget; @@ -126,7 +126,7 @@ public final class ConversationListItem extends ConstraintLayout implements Bind private AlertView alertView; private TextView unreadIndicator; private long lastSeen; - private ThreadRecord thread; + private ThreadWithRecipient thread; private boolean batchMode; private Locale locale; private String highlightSubstring; @@ -209,7 +209,7 @@ public final class ConversationListItem extends ConstraintLayout implements Bind @Override public void bind(@NonNull LifecycleOwner lifecycleOwner, - @NonNull ThreadRecord thread, + @NonNull ThreadWithRecipient thread, @NonNull RequestManager glideRequests, @NonNull Locale locale, @NonNull Set typingThreads, @@ -220,7 +220,7 @@ public final class ConversationListItem extends ConstraintLayout implements Bind } public void bindThread(@NonNull LifecycleOwner lifecycleOwner, - @NonNull ThreadRecord thread, + @NonNull ThreadWithRecipient thread, @NonNull RequestManager requestManager, @NonNull Locale locale, @NonNull Set typingThreads, @@ -467,7 +467,7 @@ public final class ConversationListItem extends ConstraintLayout implements Bind return threadId; } - public @NonNull ThreadRecord getThread() { + public @NonNull ThreadWithRecipient getThread() { return thread; } @@ -516,7 +516,7 @@ public final class ConversationListItem extends ConstraintLayout implements Bind } } - private void setStatusIcons(ThreadRecord thread) { + private void setStatusIcons(ThreadWithRecipient thread) { if (MessageTypes.isBadDecryptType(thread.getType())) { deliveryStatusIndicator.setNone(); alertView.setFailed(); @@ -556,7 +556,7 @@ public final class ConversationListItem extends ConstraintLayout implements Bind } } - private void setUnreadIndicator(ThreadRecord thread) { + private void setUnreadIndicator(ThreadWithRecipient thread) { if (thread.isRead()) { unreadIndicator.setVisibility(View.GONE); unreadMentions.setVisibility(View.GONE); @@ -596,7 +596,7 @@ public final class ConversationListItem extends ConstraintLayout implements Bind } private static @NonNull LiveData getThreadDisplayBody(@NonNull Context context, - @NonNull ThreadRecord thread, + @NonNull ThreadWithRecipient thread, @NonNull RequestManager requestManager, @Px int thumbSize, @NonNull GlideLiveDataTarget thumbTarget) @@ -734,7 +734,7 @@ public final class ConversationListItem extends ConstraintLayout implements Bind private static LiveData createFinalBodyWithMediaIcon(@NonNull Context context, @NonNull CharSequence body, - @NonNull ThreadRecord thread, + @NonNull ThreadWithRecipient thread, @NonNull RequestManager requestManager, @Px int thumbSize, @NonNull GlideLiveDataTarget thumbTarget) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItemAction.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItemAction.java index b57797444c..07cd75ffc5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItemAction.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItemAction.java @@ -13,7 +13,7 @@ import com.bumptech.glide.RequestManager; import org.thoughtcrime.securesms.BindableConversationListItem; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.conversationlist.model.ConversationSet; -import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient; import java.util.Locale; import java.util.Set; @@ -42,7 +42,7 @@ public class ConversationListItemAction extends FrameLayout implements BindableC @Override public void bind(@NonNull LifecycleOwner lifecycleOwner, - @NonNull ThreadRecord thread, + @NonNull ThreadWithRecipient thread, @NonNull RequestManager requestManager, @NonNull Locale locale, @NonNull Set typingThreads, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchModels.kt b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchModels.kt index 34ea174bfb..d5ed1ffe71 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchModels.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchModels.kt @@ -221,7 +221,7 @@ object ConversationListSearchModels { (itemView as ConversationListItem).bindThread( lifecycleOwner, - model.thread.threadRecord, + model.thread.threadWithRecipient, requestManager, Locale.getDefault(), emptySet(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/model/Conversation.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/model/Conversation.java index 04f4563a6d..7a20948b6c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/model/Conversation.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/model/Conversation.java @@ -2,13 +2,13 @@ package org.thoughtcrime.securesms.conversationlist.model; import androidx.annotation.NonNull; -import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient; public class Conversation { - private final ThreadRecord threadRecord; - private final Type type; + private final ThreadWithRecipient threadRecord; + private final Type type; - public Conversation(@NonNull ThreadRecord threadRecord) { + public Conversation(@NonNull ThreadWithRecipient threadRecord) { this.threadRecord = threadRecord; if (this.threadRecord.getThreadId() < 0) { type = Type.valueOf(this.threadRecord.getBody()); @@ -17,7 +17,7 @@ public class Conversation { } } - public @NonNull ThreadRecord getThreadRecord() { + public @NonNull ThreadWithRecipient getThreadRecord() { return threadRecord; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/model/ConversationReader.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/model/ConversationReader.java index 4915cfd0cf..5a991681fb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/model/ConversationReader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/model/ConversationReader.java @@ -5,7 +5,7 @@ import android.database.Cursor; import androidx.annotation.NonNull; import org.thoughtcrime.securesms.database.ThreadTable; -import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient; import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.recipients.Recipient; import org.signal.core.util.CursorUtil; @@ -37,7 +37,7 @@ public class ConversationReader extends ThreadTable.StaticReader { } @Override - public ThreadRecord getCurrent() { + public ThreadWithRecipient getCurrent() { if (cursor.getColumnIndex(HEADER_COLUMN[0]) == -1) { return super.getCurrent(); } else { @@ -45,7 +45,7 @@ public class ConversationReader extends ThreadTable.StaticReader { } } - private ThreadRecord buildThreadRecordForHeader() { + private ThreadWithRecipient buildThreadRecordForHeader() { Conversation.Type type = Conversation.Type.valueOf(CursorUtil.requireString(cursor, HEADER_COLUMN[0])); int count = 0; if (type == Conversation.Type.ARCHIVED_FOOTER) { @@ -60,8 +60,8 @@ public class ConversationReader extends ThreadTable.StaticReader { return buildThreadRecordForType(type, count, showTip); } - public static ThreadRecord buildThreadRecordForType(@NonNull Conversation.Type type, int count, boolean showTip) { - return new ThreadRecord.Builder(-(100 + type.ordinal())) + public static ThreadWithRecipient buildThreadRecordForType(@NonNull Conversation.Type type, int count, boolean showTip) { + return new ThreadWithRecipient.Builder(-(100 + type.ordinal())) .setBody(type.toString()) .setDate(100) .setRecipient(Recipient.UNKNOWN) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt index 51e04af0d4..a07458f785 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -311,6 +311,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat private const val INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID = "message_thread_story_parent_story_scheduled_date_latest_revision_id_index" private const val INDEX_THREAD_DATE_RECEIVED_UNREAD = "message_thread_date_received_unread_index" + private const val INDEX_COLLAPSED_STATE = "message_collapsed_state_index" private const val INDEX_DATE_SENT_FROM_TO_THREAD = "message_date_sent_from_to_thread_index" private const val INDEX_THREAD_COUNT = "message_thread_count_index" private const val INDEX_THREAD_UNREAD_COUNT = "message_thread_unread_count_index" @@ -4657,10 +4658,22 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat } fun setReactionsSeen(threadId: Long, sinceTimestamp: Long) { - val where = "$THREAD_ID = ? AND $REACTIONS_UNREAD = ?" + if (sinceTimestamp > -1) " AND $DATE_RECEIVED <= $sinceTimestamp" else "" + // The $STORY_TYPE/$PARENT_STORY_ID and "(read=0 OR reactions_unread=1 OR votes_unread=1)" predicates are required for the + // query planner to recognize that $INDEX_THREAD_DATE_RECEIVED_UNREAD (a partial index) covers this query. + // They match exactly the WHERE clause used when defining that index. + var where = """ + $THREAD_ID = ? AND + $STORY_TYPE = 0 AND + $PARENT_STORY_ID <= 0 AND + ($READ = 0 OR $REACTIONS_UNREAD = 1 OR $VOTES_UNREAD = 1) AND + $REACTIONS_UNREAD = ? + """ + if (sinceTimestamp > -1) { + where += " AND $DATE_RECEIVED <= $sinceTimestamp" + } writableDatabase - .update(TABLE_NAME) + .update("$TABLE_NAME INDEXED BY $INDEX_THREAD_DATE_RECEIVED_UNREAD") .values( REACTIONS_UNREAD to 0, REACTIONS_LAST_SEEN to System.currentTimeMillis() @@ -4681,14 +4694,20 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat } fun setVoteSeen(threadId: Long, sinceTimestamp: Long) { - val where = if (sinceTimestamp > -1) { - "$THREAD_ID = ? AND $VOTES_UNREAD = ? AND $DATE_RECEIVED <= $sinceTimestamp" - } else { - "$THREAD_ID = ? AND $VOTES_UNREAD = ?" + // See setReactionsSeen for an explanation of the extra predicates / INDEXED BY hint. + var where = """ + $THREAD_ID = ? AND + $STORY_TYPE = 0 AND + $PARENT_STORY_ID <= 0 AND + ($READ = 0 OR $REACTIONS_UNREAD = 1 OR $VOTES_UNREAD = 1) AND + $VOTES_UNREAD = ? + """ + if (sinceTimestamp > -1) { + where += " AND $DATE_RECEIVED <= $sinceTimestamp" } writableDatabase - .update(TABLE_NAME) + .update("$TABLE_NAME INDEXED BY $INDEX_THREAD_DATE_RECEIVED_UNREAD") .values( VOTES_UNREAD to 0, VOTES_LAST_SEEN to System.currentTimeMillis() @@ -4709,6 +4728,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat } fun collapsePendingCollapsibleEvents(threadId: Long, sinceTimestamp: Long) { + // Force INDEXED BY message_collapsed_state_index. COLLAPSED_STATE = PENDING_COLLAPSED is a transient state, so the index + // entries for it are typically near zero — much more selective than scanning the thread by date_received. val where = if (sinceTimestamp > -1) { "$THREAD_ID = ? AND $COLLAPSED_STATE = ? AND $DATE_RECEIVED <= $sinceTimestamp" } else { @@ -4716,7 +4737,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat } writableDatabase - .update(TABLE_NAME) + .update("$TABLE_NAME INDEXED BY $INDEX_COLLAPSED_STATE") .values(COLLAPSED_STATE to CollapsedState.COLLAPSED.id) .where(where, threadId, CollapsedState.PENDING_COLLAPSED.id) .run() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt index a883d5a19e..bf12604c51 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt @@ -70,7 +70,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase.Companion.threads import org.thoughtcrime.securesms.database.model.DistributionListId import org.thoughtcrime.securesms.database.model.KeyTransparencyStore import org.thoughtcrime.securesms.database.model.RecipientRecord -import org.thoughtcrime.securesms.database.model.ThreadRecord +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient import org.thoughtcrime.securesms.database.model.databaseprotos.BadgeList import org.thoughtcrime.securesms.database.model.databaseprotos.DeviceLastResetTime import org.thoughtcrime.securesms.database.model.databaseprotos.ExpiringProfileKeyCredentialColumnData @@ -3809,7 +3809,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da val recipientsWithinInteractionThreshold: MutableSet = LinkedHashSet() threadDatabase.readerFor(threadDatabase.getRecentPushConversationList(-1)).use { reader -> - var record: ThreadRecord? = reader.getNext() + var record: ThreadWithRecipient? = reader.getNext() while (record != null && record.date > lastInteractionThreshold) { val recipient = Recipient.resolved(record.recipient.id) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTableCursorUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTableCursorUtil.kt index e3da3c1327..9d1b0d1b5b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTableCursorUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTableCursorUtil.kt @@ -209,7 +209,7 @@ object RecipientTableCursorUtil { identityKey = cursor.optionalString(RecipientTable.IDENTITY_KEY).map { Base64.decodeOrThrow(it) }.orElse(null), identityStatus = cursor.optionalInt(RecipientTable.IDENTITY_STATUS).map { VerifiedStatus.forState(it) }.orElse(VerifiedStatus.DEFAULT), isArchived = cursor.optionalBoolean(ThreadTable.ARCHIVED).orElse(false), - isForcedUnread = cursor.optionalInt(ThreadTable.READ).map { status: Int -> status == ThreadTable.ReadStatus.FORCED_UNREAD.serialize() }.orElse(false), + isForcedUnread = cursor.optionalInt(ThreadTable.READ).map { status: Int -> status == ThreadTable.ReadStatus.ForcedUnread.serialize() }.orElse(false), unregisteredTimestamp = cursor.optionalLong(RecipientTable.UNREGISTERED_TIMESTAMP).orElse(0), systemNickname = cursor.optionalString(RecipientTable.SYSTEM_NICKNAME).orElse(null), pniSignatureVerified = cursor.optionalBoolean(RecipientTable.PNI_SIGNATURE_VERIFIED).orElse(false) 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 91519cc78f..679679cb30 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt @@ -26,9 +26,12 @@ import org.signal.core.util.readToSingleBoolean import org.signal.core.util.readToSingleInt import org.signal.core.util.readToSingleIntOrNull import org.signal.core.util.readToSingleLong +import org.signal.core.util.readToSingleObject +import org.signal.core.util.requireBlob import org.signal.core.util.requireBoolean import org.signal.core.util.requireInt import org.signal.core.util.requireLong +import org.signal.core.util.requireLongOrNull import org.signal.core.util.requireString import org.signal.core.util.select import org.signal.core.util.toInt @@ -51,6 +54,7 @@ import org.thoughtcrime.securesms.database.ThreadBodyUtil.ThreadBody import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.ThreadRecord +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras import org.thoughtcrime.securesms.database.model.serialize @@ -131,7 +135,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa $DATE INTEGER DEFAULT 0, $MEANINGFUL_MESSAGES INTEGER DEFAULT 0, $RECIPIENT_ID INTEGER NOT NULL UNIQUE REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE, - $READ INTEGER DEFAULT ${ReadStatus.READ.serialize()}, + $READ INTEGER DEFAULT ${ReadStatus.Read.serialize()}, $TYPE INTEGER DEFAULT 0, $ERROR INTEGER DEFAULT 0, $SNIPPET TEXT, @@ -481,7 +485,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa writableDatabase .updateAll(TABLE_NAME) .values( - READ to ReadStatus.READ.serialize(), + READ to ReadStatus.Read.serialize(), UNREAD_COUNT to 0, UNREAD_SELF_MENTION_COUNT to 0 ) @@ -565,7 +569,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa writableDatabase.withinTransaction { db -> for ((threadId, sinceTimestamp) in threadIdToSinceTimestamp) { - val previous = getThreadRecord(threadId) + val previous = getRecipientIdAndRead(threadId) messageRecords += messages.setMessagesReadSince(threadId, sinceTimestamp) @@ -578,7 +582,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa val lastSeenTimestamp = messages.getMostRecentReadMessageDateReceived(threadId) ?: System.currentTimeMillis() val contentValues = contentValuesOf( - READ to ReadStatus.READ.serialize(), + READ to ReadStatus.Read.serialize(), UNREAD_COUNT to unreadCount, UNREAD_SELF_MENTION_COUNT to unreadMentionsCount, LAST_SEEN to lastSeenTimestamp @@ -589,8 +593,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa .where("$ID = ?", threadId) .run() - if (previous != null && previous.isForcedUnread) { - recipients.markNeedsSync(previous.recipient.id) + if (previous != null && previous.read == ReadStatus.ForcedUnread) { + recipients.markNeedsSync(previous.recipientId) needsSync = true } } @@ -611,7 +615,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa writableDatabase.withinTransaction { db -> val query = SqlUtil.buildSingleCollectionQuery(ID, threadIds) - val contentValues = contentValuesOf(READ to ReadStatus.FORCED_UNREAD.serialize()) + val contentValues = contentValuesOf(READ to ReadStatus.ForcedUnread.serialize()) db.update(TABLE_NAME, contentValues, query.where, query.whereArgs) recipientIds = getRecipientIdsForThreadIds(threadIds) @@ -657,7 +661,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa val forcedUnreadCount: Long = readableDatabase .select("COUNT(*)") .from(TABLE_NAME) - .where("$READ = ? AND $ARCHIVED = ?", ReadStatus.FORCED_UNREAD.serialize(), 0) + .where("$READ = ? AND $ARCHIVED = ?", ReadStatus.ForcedUnread.serialize(), 0) .run() .use { cursor -> if (cursor.moveToFirst()) { @@ -737,7 +741,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa LEFT OUTER JOIN ${RecipientTable.TABLE_NAME} ON $TABLE_NAME.$RECIPIENT_ID = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} WHERE $ARCHIVED = 0 AND - $READ = ${ThreadTable.ReadStatus.FORCED_UNREAD.serialize()} + $READ = ${ThreadTable.ReadStatus.ForcedUnread.serialize()} $chatFolderQuery """ val forcedUnreadCount = readableDatabase.rawQuery(forcedUnreadCountQuery, null).readToSingleInt(0) @@ -777,7 +781,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa return readableDatabase .select(*aggregator) .from(TABLE_NAME) - .where("$READ != ${ReadStatus.READ.serialize()} AND $ARCHIVED = 0 AND $MEANINGFUL_MESSAGES != 0") + .where("$READ != ${ReadStatus.Read.serialize()} AND $ARCHIVED = 0 AND $MEANINGFUL_MESSAGES != 0") .run() .use(mapCursorToType) } @@ -786,7 +790,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa writableDatabase.execSQL( """ UPDATE $TABLE_NAME - SET $READ = ${ReadStatus.UNREAD.serialize()}, + SET $READ = ${ReadStatus.Unread.serialize()}, $UNREAD_COUNT = $UNREAD_COUNT + ?, $UNREAD_SELF_MENTION_COUNT = $UNREAD_SELF_MENTION_COUNT + ?, $LAST_SCROLLED = ? @@ -852,7 +856,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } selection += if (unreadOnly) { - ") AND $TABLE_NAME.$READ != ${ReadStatus.READ.serialize()}" + ") AND $TABLE_NAME.$READ != ${ReadStatus.Read.serialize()}" } else { ")" } @@ -1570,14 +1574,14 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } fun updateReadState(threadId: Long) { - val previous = getThreadRecord(threadId) + val previous = getRecipientIdAndRead(threadId) val unreadCount = messages.getUnreadCount(threadId) val unreadMentionsCount = messages.getUnreadMentionCount(threadId) writableDatabase .update(TABLE_NAME) .values( - READ to if (unreadCount == 0) ReadStatus.READ.serialize() else ReadStatus.UNREAD.serialize(), + READ to if (unreadCount == 0) ReadStatus.Read.serialize() else ReadStatus.Unread.serialize(), UNREAD_COUNT to unreadCount, UNREAD_SELF_MENTION_COUNT to unreadMentionsCount ) @@ -1586,8 +1590,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa notifyConversationListListeners() - if (previous != null && previous.isForcedUnread) { - recipients.markNeedsSync(previous.recipient.id) + if (previous != null && previous.read == ReadStatus.ForcedUnread) { + recipients.markNeedsSync(previous.recipientId) StorageSyncHelper.scheduleSyncForDataChange() } } @@ -1670,12 +1674,12 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa val threadId: Long? = if (archived) getOrCreateThreadIdFor(recipientId, isGroup) else getThreadIdFor(recipientId) if (forcedUnread) { - values.put(READ, ReadStatus.FORCED_UNREAD.serialize()) + values.put(READ, ReadStatus.ForcedUnread.serialize()) } else if (threadId != null) { val unreadCount = messages.getUnreadCount(threadId) val unreadMentionsCount = messages.getUnreadMentionCount(threadId) - values.put(READ, if (unreadCount == 0) ReadStatus.READ.serialize() else ReadStatus.UNREAD.serialize()) + values.put(READ, if (unreadCount == 0) ReadStatus.Read.serialize() else ReadStatus.Unread.serialize()) values.put(UNREAD_COUNT, unreadCount) values.put(UNREAD_SELF_MENTION_COUNT, unreadMentionsCount) } @@ -1898,10 +1902,6 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa .run() } - fun getThreadRecordFor(recipient: Recipient): ThreadRecord { - return getThreadRecord(getOrCreateThreadIdFor(recipient))!! - } - fun getAllThreadRecipients(): Set { return readableDatabase .select(RECIPIENT_ID) @@ -1988,14 +1988,101 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa return null } - val query = createQuery("$TABLE_NAME.$ID = ?", 1) - - return readableDatabase.rawQuery(query, SqlUtil.buildArgs(threadId)).use { cursor -> - if (cursor.moveToFirst()) { - readerFor(cursor).getCurrent() - } else { - null + return readableDatabase + .select( + ID, + DATE, + MEANINGFUL_MESSAGES, + RECIPIENT_ID, + READ, + TYPE, + ERROR, + SNIPPET, + SNIPPET_TYPE, + SNIPPET_URI, + SNIPPET_CONTENT_TYPE, + SNIPPET_EXTRAS, + UNREAD_COUNT, + ARCHIVED, + STATUS, + HAS_DELIVERY_RECEIPT, + HAS_READ_RECEIPT, + EXPIRES_IN, + LAST_SEEN, + HAS_SENT, + LAST_SCROLLED, + PINNED_ORDER, + UNREAD_SELF_MENTION_COUNT, + ACTIVE, + SNIPPET_MESSAGE_EXTRAS, + SNIPPET_MESSAGE_ID + ) + .from(TABLE_NAME) + .where("$ID = ?", threadId) + .run() + .readToSingleObject { cursor -> + ThreadRecord( + threadId = cursor.requireLong(ID), + date = cursor.requireLong(DATE), + meaningfulMessages = cursor.requireBoolean(MEANINGFUL_MESSAGES), + recipientId = RecipientId.from(cursor.requireLong(RECIPIENT_ID)), + read = ReadStatus.deserialize(cursor.requireInt(READ)), + type = cursor.requireLong(TYPE), + error = cursor.requireInt(ERROR), + snippet = cursor.requireString(SNIPPET), + snippetType = cursor.requireLong(SNIPPET_TYPE), + snippetUri = parseSnippetUri(cursor.requireString(SNIPPET_URI)), + snippetContentType = cursor.requireString(SNIPPET_CONTENT_TYPE), + snippetExtras = parseSnippetExtras(cursor.requireString(SNIPPET_EXTRAS)), + unreadCount = cursor.requireInt(UNREAD_COUNT), + archived = cursor.requireBoolean(ARCHIVED), + status = cursor.requireLong(STATUS), + hasDeliveryReceipt = cursor.requireBoolean(HAS_DELIVERY_RECEIPT), + hasReadReceipt = cursor.requireBoolean(HAS_READ_RECEIPT), + expiresIn = cursor.requireLong(EXPIRES_IN), + lastSeen = cursor.requireLong(LAST_SEEN), + hasSent = cursor.requireBoolean(HAS_SENT), + lastScrolled = cursor.requireLong(LAST_SCROLLED), + pinnedOrder = cursor.requireLongOrNull(PINNED_ORDER), + unreadSelfMentionCount = cursor.requireInt(UNREAD_SELF_MENTION_COUNT), + active = cursor.requireBoolean(ACTIVE), + snippetMessageExtras = cursor.requireBlob(SNIPPET_MESSAGE_EXTRAS)?.let { MessageExtras.ADAPTER.decode(it) }, + snippetMessageId = cursor.requireLong(SNIPPET_MESSAGE_ID) + ) } + } + + private fun parseSnippetUri(uriString: String?): Uri? { + if (uriString == null) return null + return try { + Uri.parse(uriString) + } catch (e: IllegalArgumentException) { + Log.w(TAG, e) + null + } + } + + private fun parseSnippetExtras(extraString: String?): Extra? { + if (extraString == null) return null + return try { + val jsonObject = SaneJSONObject(JSONObject(extraString)) + Extra( + isViewOnce = jsonObject.getBoolean("isRevealable"), + isSticker = jsonObject.getBoolean("isSticker"), + stickerEmoji = jsonObject.getString("stickerEmoji"), + isAlbum = jsonObject.getBoolean("isAlbum"), + deletedBy = jsonObject.getString("deletedBy"), + isMessageRequestAccepted = jsonObject.getBoolean("isMessageRequestAccepted"), + isGv2Invite = jsonObject.getBoolean("isGv2Invite"), + groupAddedBy = jsonObject.getString("groupAddedBy"), + individualRecipientId = jsonObject.getString("individualRecipientId")!!, + bodyRanges = jsonObject.getString("bodyRanges"), + isScheduled = jsonObject.getBoolean("isScheduled"), + isRecipientHidden = jsonObject.getBoolean("isRecipientHidden"), + isPoll = jsonObject.getBoolean("isPoll") + ) + } catch (e: Exception) { + null } } @@ -2016,7 +2103,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa val contentValues = contentValuesOf( DATE to 0, MEANINGFUL_MESSAGES to 0, - READ to ReadStatus.READ.serialize(), + READ to ReadStatus.Read.serialize(), TYPE to 0, ERROR to 0, SNIPPET to null, @@ -2196,6 +2283,20 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa return query } + private fun getRecipientIdAndRead(threadId: Long): RecipientIdAndRead? { + return readableDatabase + .select(RECIPIENT_ID, READ) + .from(TABLE_NAME) + .where("$ID = $threadId") + .run() + .readToSingleObject { + RecipientIdAndRead( + recipientId = RecipientId.from(it.requireLong(RECIPIENT_ID)), + read = ReadStatus.deserialize(it.requireInt(READ)) + ) + } + } + private fun isSilentType(type: Long): Boolean { return MessageTypes.isProfileChange(type) || MessageTypes.isGroupV1MigrationEvent(type) || @@ -2235,7 +2336,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } if (this.showUnread) { - fullQuery.add("$UNREAD_COUNT > 0 OR $READ == ${ReadStatus.FORCED_UNREAD.serialize()}") + fullQuery.add("$UNREAD_COUNT > 0 OR $READ == ${ReadStatus.ForcedUnread.serialize()}") } if (!this.showMutedChats) { @@ -2249,7 +2350,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa return when (this) { ConversationFilter.OFF -> "" //language=sql - ConversationFilter.UNREAD -> " AND ($UNREAD_COUNT > 0 OR $READ == ${ReadStatus.FORCED_UNREAD.serialize()})" + ConversationFilter.UNREAD -> " AND ($UNREAD_COUNT > 0 OR $READ == ${ReadStatus.ForcedUnread.serialize()})" ConversationFilter.MUTED -> error("This filter selection isn't supported yet.") ConversationFilter.GROUPS -> error("This filter selection isn't supported yet.") } @@ -2266,7 +2367,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa inner class Reader(cursor: Cursor) : StaticReader(cursor, context) open class StaticReader(private val cursor: Cursor, private val context: Context) : Closeable { - fun getNext(): ThreadRecord? { + fun getNext(): ThreadWithRecipient? { return if (!cursor.moveToNext()) { null } else { @@ -2274,7 +2375,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } } - open fun getCurrent(): ThreadRecord? { + open fun getCurrent(): ThreadWithRecipient? { val recipientId = RecipientId.from(cursor.requireLong(RECIPIENT_ID)) val recipientSettings = RecipientTableCursorUtil.getRecord(context, cursor, RECIPIENT_ID) @@ -2319,7 +2420,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa null } - return ThreadRecord.Builder(cursor.requireLong(ID)) + return ThreadWithRecipient.Builder(cursor.requireLong(ID)) .setRecipient(recipient) .setType(cursor.requireLong(SNIPPET_TYPE)) .setDistributionType(cursor.requireInt(TYPE)) @@ -2335,7 +2436,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa .setContentType(cursor.requireString(SNIPPET_CONTENT_TYPE)) .setMeaningfulMessages(cursor.requireLong(MEANINGFUL_MESSAGES) > 0) .setUnreadCount(cursor.requireInt(UNREAD_COUNT)) - .setForcedUnread(cursor.requireInt(READ) == ReadStatus.FORCED_UNREAD.serialize()) + .setForcedUnread(cursor.requireInt(READ) == ReadStatus.ForcedUnread.serialize()) .setPinned(cursor.requireBoolean(PINNED_ORDER)) .setUnreadSelfMentionsCount(cursor.requireInt(UNREAD_SELF_MENTION_COUNT)) .setExtra(extra) @@ -2454,10 +2555,10 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } } - internal enum class ReadStatus(private val value: Int) { - READ(1), - UNREAD(0), - FORCED_UNREAD(2); + enum class ReadStatus(private val value: Int) { + Read(1), + Unread(0), + ForcedUnread(2); fun serialize(): Int { return value @@ -2491,4 +2592,9 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa ) data class ThreadDeleteSyncInfo(val threadId: Long, val addressableMessages: Set, val nonExpiringAddressableMessages: Set) + + data class RecipientIdAndRead( + val recipientId: RecipientId, + val read: ReadStatus + ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.kt new file mode 100644 index 0000000000..60bb6f5913 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ +package org.thoughtcrime.securesms.database.model + +import android.net.Uri +import org.thoughtcrime.securesms.database.ThreadTable +import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras +import org.thoughtcrime.securesms.recipients.RecipientId + +/** + * Represents a single row in the thread table. + */ +data class ThreadRecord( + val threadId: Long, + val date: Long, + val meaningfulMessages: Boolean, + val recipientId: RecipientId, + val read: ThreadTable.ReadStatus, + val type: Long, + val error: Int, + val snippet: String?, + val snippetType: Long, + val snippetUri: Uri?, + val snippetContentType: String?, + val snippetExtras: ThreadTable.Extra?, + val unreadCount: Int, + val archived: Boolean, + val status: Long, + val hasDeliveryReceipt: Boolean, + val hasReadReceipt: Boolean, + val expiresIn: Long, + val lastSeen: Long, + val hasSent: Boolean, + val lastScrolled: Long, + val pinnedOrder: Long?, + val unreadSelfMentionCount: Int, + val active: Boolean, + val snippetMessageExtras: MessageExtras?, + val snippetMessageId: Long +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadWithRecipient.java similarity index 97% rename from app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java rename to app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadWithRecipient.java index 249d198a97..09ebbc7f11 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadWithRecipient.java @@ -34,9 +34,9 @@ import org.signal.network.util.Preconditions; import java.util.Objects; /** - * Represents an entry in the {@link org.thoughtcrime.securesms.database.ThreadTable}. + * Similar to {@link ThreadRecord}, but with a populated recipient. */ -public final class ThreadRecord { +public final class ThreadWithRecipient { private final long threadId; private final String body; @@ -60,7 +60,7 @@ public final class ThreadRecord { private final int unreadSelfMentionsCount; private final MessageExtras messageExtras; - private ThreadRecord(@NonNull Builder builder) { + private ThreadWithRecipient(@NonNull Builder builder) { this.threadId = builder.threadId; this.body = builder.body; this.recipient = builder.recipient; @@ -255,7 +255,7 @@ public final class ThreadRecord { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - ThreadRecord that = (ThreadRecord) o; + ThreadWithRecipient that = (ThreadWithRecipient) o; return threadId == that.threadId && type == that.type && date == that.date && @@ -434,13 +434,13 @@ public final class ThreadRecord { return this; } - public ThreadRecord build() { + public ThreadWithRecipient build() { if (distributionType == ThreadTable.DistributionTypes.CONVERSATION) { Preconditions.checkArgument(threadId > 0); Preconditions.checkNotNull(body); Preconditions.checkNotNull(recipient); } - return new ThreadRecord(this); + return new ThreadWithRecipient(this); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java index 98ec2b5743..5d772d7e65 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java @@ -391,6 +391,7 @@ class JobController { } } + jobStorage.markJobAsRunning(job.getId(), System.currentTimeMillis()); runningJobs.put(job.getId(), new ActiveJobInfo(job, runnerName, timeoutMs == 0)); jobTracker.onStateChange(job, JobTracker.JobState.RUNNING); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ConversationShortcutUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ConversationShortcutUpdateJob.java index fef9a777e3..96bc034f37 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ConversationShortcutUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ConversationShortcutUpdateJob.java @@ -6,7 +6,7 @@ import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.ThreadTable; -import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient; import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.keyvalue.SignalStore; @@ -74,7 +74,7 @@ public class ConversationShortcutUpdateJob extends BaseJob { List ranked = new ArrayList<>(maxShortcuts); try (ThreadTable.Reader reader = threadTable.readerFor(threadTable.getRecentConversationList(maxShortcuts, false, false, false, true, true, false))) { - ThreadRecord record; + ThreadWithRecipient record; while ((record = reader.getNext()) != null) { ranked.add(record.getRecipient().resolve()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt index ec27eb08f3..6128491fbd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/DataMessageProcessor.kt @@ -533,13 +533,12 @@ object DataMessageProcessor { return null } - val targetThread = SignalDatabase.threads.getThreadRecord(targetMessage.threadId) - if (targetThread == null) { + val targetThreadRecipientId = SignalDatabase.threads.getRecipientIdForThreadId(targetMessage.threadId) + if (targetThreadRecipientId == null) { warn(envelope.clientTimestamp!!, "[handleReaction] Could not find a thread for the message! timestamp: " + targetSentTimestamp + " author: " + targetAuthor.id) return null } - val targetThreadRecipientId = targetThread.recipient.id val groupRecord = SignalDatabase.groups.getGroup(targetThreadRecipientId).orNull() if (groupRecord != null && !groupRecord.members.contains(senderRecipientId)) { warn(envelope.clientTimestamp!!, "[handleReaction] Reaction author is not in the group! timestamp: " + targetSentTimestamp + " author: " + targetAuthor.id) @@ -1275,13 +1274,13 @@ object DataMessageProcessor { return null } - val targetThread = SignalDatabase.threads.getThreadRecord(targetMessage.threadId) - if (targetThread == null) { + val targetThreadRecipientId = SignalDatabase.threads.getRecipientIdForThreadId(targetMessage.threadId) + if (targetThreadRecipientId == null) { warn(envelope.clientTimestamp!!, "[handlePinMessage] Could not find a thread for the message! timestamp: ${pinMessage.targetSentTimestamp}") return null } - if (targetThread.recipient.id != threadRecipient.id) { + if (targetThreadRecipientId != threadRecipient.id) { warn(envelope.clientTimestamp!!, "[handlePinMessage] Target message is in a different thread than the thread recipient! timestamp: ${pinMessage.targetSentTimestamp}") return null } @@ -1367,13 +1366,13 @@ object DataMessageProcessor { return null } - val targetThread = SignalDatabase.threads.getThreadRecord(targetMessage.threadId) - if (targetThread == null) { + val targetThreadRecipientId = SignalDatabase.threads.getRecipientIdForThreadId(targetMessage.threadId) + if (targetThreadRecipientId == null) { warn(envelope.clientTimestamp!!, "[handleUnpinMessage] Could not find a thread for the message! timestamp: ${unpinMessage.targetSentTimestamp}") return null } - if (targetThread.recipient.id != threadRecipient.id) { + if (targetThreadRecipientId != threadRecipient.id) { warn(envelope.clientTimestamp!!, "[handleUnpinMessage] Target message is in a different thread than the thread recipient! timestamp: ${unpinMessage.targetSentTimestamp}") return null } @@ -1427,13 +1426,12 @@ object DataMessageProcessor { return null } - val targetThread = SignalDatabase.threads.getThreadRecord(targetMessage.threadId) - if (targetThread == null) { + val targetThreadRecipientId = SignalDatabase.threads.getRecipientIdForThreadId(targetMessage.threadId) + if (targetThreadRecipientId == null) { warn(envelope.clientTimestamp!!, "[handleAdminRemoteDelete] Could not find a thread for the message! timestamp: $targetSentTimestamp author: ${targetAuthor.id}") return null } - val targetThreadRecipientId = targetThread.recipient.id if (targetThreadRecipientId != threadRecipient.id) { warn(envelope.clientTimestamp!!, "[handleAdminRemoteDelete] Target message is in a different thread than the admin delete! timestamp: $targetSentTimestamp") return null @@ -1616,17 +1614,17 @@ object DataMessageProcessor { return null } - val targetThread = SignalDatabase.threads.getThreadRecord(targetMessage.threadId) - if (targetThread == null) { + val targetThreadRecipientId = SignalDatabase.threads.getRecipientIdForThreadId(targetMessage.threadId) + if (targetThreadRecipientId == null) { warn(envelope.clientTimestamp!!, "[handlePollValidation] Could not find a thread for the message. timestamp: $targetSentTimestamp author: ${targetAuthor.id}") return null } - val groupRecord = SignalDatabase.groups.getGroup(targetThread.recipient.id).orNull() + val groupRecord = SignalDatabase.groups.getGroup(targetThreadRecipientId).orNull() if (groupRecord != null && !groupRecord.members.contains(senderRecipient.id)) { warn(envelope.clientTimestamp!!, "[handlePollValidation] Sender is not in the group. timestamp: $targetSentTimestamp author: ${targetAuthor.id}") return null - } else if (groupRecord == null && senderRecipient.id != targetThread.recipient.id && senderRecipient.id != Recipient.self().id) { + } else if (groupRecord == null && senderRecipient.id != targetThreadRecipientId && senderRecipient.id != Recipient.self().id) { warn(envelope.clientTimestamp!!, "[handlePollValidation] Sender is not a part of the 1:1 thread!") return null } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java index 3a575ad2e8..f715e1b1cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java @@ -9,7 +9,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; -import org.jetbrains.annotations.NotNull; import org.signal.core.util.ThreadUtil; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; @@ -17,8 +16,7 @@ import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.RecipientTable.MissingRecipientException; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.ThreadTable; -import org.thoughtcrime.securesms.database.model.RecipientRecord; -import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.signal.core.util.CursorUtil; import org.signal.core.util.LRUCache; @@ -252,8 +250,8 @@ public final class LiveRecipientCache { List recipients = new ArrayList<>(); try (ThreadTable.Reader reader = threadTable.readerFor(threadTable.getRecentConversationList(THREAD_CACHE_WARM_MAX, false, false))) { - int i = 0; - ThreadRecord record = null; + int i = 0; + ThreadWithRecipient record = null; while ((record = reader.getNext()) != null && i < THREAD_CACHE_WARM_MAX) { recipients.add(record.getRecipient()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java index 242e938a42..7f12c38222 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java @@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.database.ThreadTable; import org.thoughtcrime.securesms.database.model.GroupRecord; import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient; import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList; import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.recipients.Recipient; @@ -82,8 +82,8 @@ public class SearchRepository { @WorkerThread public @NonNull ThreadSearchResult queryThreadsSync(@NonNull String query, boolean unreadOnly) { - long start = System.currentTimeMillis(); - List result = queryConversations(query, unreadOnly); + long start = System.currentTimeMillis(); + List result = queryConversations(query, unreadOnly); Log.d(TAG, "[threads] Search took " + (System.currentTimeMillis() - start) + " ms"); @@ -121,7 +121,7 @@ public class SearchRepository { }); } - private @NonNull List queryConversations(@NonNull String query, boolean unreadOnly) { + private @NonNull List queryConversations(@NonNull String query, boolean unreadOnly) { if (Util.isEmpty(query)) { return Collections.emptyList(); } @@ -148,7 +148,7 @@ public class SearchRepository { } } - LinkedHashSet output = new LinkedHashSet<>(); + LinkedHashSet output = new LinkedHashSet<>(); output.addAll(getMatchingThreads(contactIds, unreadOnly)); output.addAll(getMatchingThreads(groupsByTitleIds, unreadOnly)); @@ -156,7 +156,7 @@ public class SearchRepository { return new ArrayList<>(output); } - private List getMatchingThreads(@NonNull Collection recipientIds, boolean unreadOnly) { + private List getMatchingThreads(@NonNull Collection recipientIds, boolean unreadOnly) { try (Cursor cursor = threadTable.getFilteredConversationList(new ArrayList<>(recipientIds), unreadOnly)) { return readToList(cursor, new ThreadModelBuilder(threadTable)); } @@ -455,7 +455,7 @@ public class SearchRepository { return combined; } - private static class ThreadModelBuilder implements ModelBuilder { + private static class ThreadModelBuilder implements ModelBuilder { private final ThreadTable threadTable; @@ -464,7 +464,7 @@ public class SearchRepository { } @Override - public ThreadRecord build(@NonNull Cursor cursor) { + public ThreadWithRecipient build(@NonNull Cursor cursor) { return threadTable.readerFor(cursor).getCurrent(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/ThreadSearchResult.kt b/app/src/main/java/org/thoughtcrime/securesms/search/ThreadSearchResult.kt index e50673189b..3bd1b9aee3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/ThreadSearchResult.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/search/ThreadSearchResult.kt @@ -1,5 +1,5 @@ package org.thoughtcrime.securesms.search -import org.thoughtcrime.securesms.database.model.ThreadRecord +import org.thoughtcrime.securesms.database.model.ThreadWithRecipient -data class ThreadSearchResult(val results: List, val query: String) +data class ThreadSearchResult(val results: List, val query: String)