Update last seen logic to used last read message instead of now timestamp.

This commit is contained in:
Cody Henthorne
2025-02-11 16:13:55 -05:00
committed by Greyson Parrelli
parent d7221a384b
commit b82d16abcb
15 changed files with 83 additions and 69 deletions

View File

@@ -55,7 +55,7 @@ public class TurnOffContactJoinedNotificationsActivity extends AppCompatActivity
SimpleTask.run(getLifecycle(), () -> {
ThreadTable threadTable = SignalDatabase.threads();
List<MessageTable.MarkedMessageInfo> marked = threadTable.setRead(getIntent().getLongExtra(EXTRA_THREAD_ID, -1), false);
List<MessageTable.MarkedMessageInfo> marked = threadTable.setRead(getIntent().getLongExtra(EXTRA_THREAD_ID, -1));
MarkReadReceiver.process(marked);
SignalStore.settings().setNotifyWhenContactJoinsSignal(false);

View File

@@ -58,7 +58,7 @@ public class ConversationRepository {
boolean showUniversalExpireTimerUpdate = false;
if (lastSeen > 0) {
lastSeenPosition = SignalDatabase.messages().getMessagePositionOnOrAfterTimestamp(threadId, lastSeen);
lastSeenPosition = SignalDatabase.messages().getMessagePositionByDateReceivedTimestamp(threadId, lastSeen, false);
}
if (lastSeenPosition <= 0) {
@@ -66,7 +66,7 @@ public class ConversationRepository {
}
if (lastSeen == 0 && lastScrolled > 0) {
lastScrolledPosition = SignalDatabase.messages().getMessagePositionOnOrAfterTimestamp(threadId, lastScrolled);
lastScrolledPosition = SignalDatabase.messages().getMessagePositionByDateReceivedTimestamp(threadId, lastScrolled, true);
}
if (!isMessageRequestAccepted) {

View File

@@ -62,7 +62,7 @@ public class MarkReadHelper {
debouncer.publish(() -> {
EXECUTOR.execute(() -> {
ThreadTable threadTable = SignalDatabase.threads();
List<MessageTable.MarkedMessageInfo> infos = threadTable.setReadSince(conversationId, false, timestamp);
List<MessageTable.MarkedMessageInfo> infos = threadTable.setReadSince(conversationId, timestamp);
Log.d(TAG, "Marking " + infos.size() + " messages as read.");

View File

@@ -702,8 +702,6 @@ class ConversationFragment :
inputPanel.onPause()
viewModel.markLastSeen()
EventBus.getDefault().unregister(this)
}

View File

@@ -542,12 +542,6 @@ class ConversationRepository(
AppDependencies.expiringMessageManager.scheduleDeletion(expirationInfos)
}
fun markLastSeen(threadId: Long) {
SignalExecutors.BOUNDED_IO.execute {
SignalDatabase.threads.setLastSeen(threadId)
}
}
fun getEarliestMessageSentDate(threadId: Long): Single<Long> {
return Single
.fromCallable { SignalDatabase.messages.getEarliestMessageSentDate(threadId) }

View File

@@ -577,10 +577,6 @@ class ConversationViewModel(
.observeOn(AndroidSchedulers.mainThread())
}
fun markLastSeen() {
repository.markLastSeen(threadId)
}
fun onChatSearchOpened() {
// Trigger the lazy load, so we can race initialization of the validator
_jumpToDateValidator

View File

@@ -1156,7 +1156,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
stopwatch.split("task-start");
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(ids, false);
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(ids);
stopwatch.split("db");
AppDependencies.getMessageNotifier().updateNotification(context);

View File

@@ -271,7 +271,7 @@ class ConversationListViewModel(
fun markChatFolderRead(chatFolder: ChatFolderRecord) {
viewModelScope.launch(Dispatchers.IO) {
val ids = SignalDatabase.threads.getThreadIdsByChatFolder(chatFolder)
val messageIds = SignalDatabase.threads.setRead(ids, false)
val messageIds = SignalDatabase.threads.setRead(ids)
AppDependencies.messageNotifier.updateNotification(AppDependencies.application)
MarkReadReceiver.process(messageIds)
}

View File

@@ -734,7 +734,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
fun getViewedIncomingMessages(threadId: Long): List<MarkedMessageInfo> {
return readableDatabase
.select(ID, FROM_RECIPIENT_ID, DATE_SENT, TYPE, THREAD_ID, STORY_TYPE)
.select(ID, FROM_RECIPIENT_ID, DATE_RECEIVED, DATE_SENT, TYPE, THREAD_ID, STORY_TYPE)
.from(TABLE_NAME)
.where("$THREAD_ID = ? AND $VIEWED_COLUMN > 0 AND $TYPE & ${MessageTypes.BASE_INBOX_TYPE} = ${MessageTypes.BASE_INBOX_TYPE}", threadId)
.run()
@@ -756,7 +756,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
val results: List<MarkedMessageInfo> = readableDatabase
.select(ID, FROM_RECIPIENT_ID, DATE_SENT, TYPE, THREAD_ID, STORY_TYPE)
.select(ID, FROM_RECIPIENT_ID, DATE_SENT, DATE_RECEIVED, TYPE, THREAD_ID, STORY_TYPE)
.from(TABLE_NAME)
.where("$ID IN (${Util.join(messageIds, ",")}) AND $VIEWED_COLUMN = 0")
.run()
@@ -803,7 +803,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
fun setOutgoingGiftsRevealed(messageIds: List<Long>): List<MarkedMessageInfo> {
val results: List<MarkedMessageInfo> = readableDatabase
.select(ID, TO_RECIPIENT_ID, DATE_SENT, THREAD_ID, STORY_TYPE)
.select(ID, TO_RECIPIENT_ID, DATE_SENT, DATE_RECEIVED, THREAD_ID, STORY_TYPE)
.from(TABLE_NAME)
.where("""$ID IN (${Util.join(messageIds, ",")}) AND ($outgoingTypeClause) AND ($TYPE & ${MessageTypes.SPECIAL_TYPES_MASK} = ${MessageTypes.SPECIAL_TYPE_GIFT_BADGE}) AND $VIEWED_COLUMN = 0""")
.run()
@@ -2346,13 +2346,14 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
UPDATE $TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID
SET $READ = 1, $REACTIONS_UNREAD = 0, $REACTIONS_LAST_SEEN = ${System.currentTimeMillis()}
WHERE $where
RETURNING $ID, $FROM_RECIPIENT_ID, $DATE_SENT, $TYPE, $EXPIRES_IN, $EXPIRE_STARTED, $THREAD_ID, $STORY_TYPE
RETURNING $ID, $FROM_RECIPIENT_ID, $DATE_SENT, $DATE_RECEIVED, $TYPE, $EXPIRES_IN, $EXPIRE_STARTED, $THREAD_ID, $STORY_TYPE
""",
arguments ?: emptyArray()
).readToList { cursor ->
val threadId = cursor.requireLong(THREAD_ID)
val recipientId = RecipientId.from(cursor.requireLong(FROM_RECIPIENT_ID))
val dateSent = cursor.requireLong(DATE_SENT)
val dateReceived = cursor.requireLong(DATE_RECEIVED)
val messageId = cursor.requireLong(ID)
val expiresIn = cursor.requireLong(EXPIRES_IN)
val expireStarted = cursor.requireLong(EXPIRE_STARTED)
@@ -2361,7 +2362,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val storyType = fromCode(CursorUtil.requireInt(cursor, STORY_TYPE))
if (recipientId != releaseChannelId) {
MarkedMessageInfo(threadId, syncMessageId, MessageId(messageId), expirationInfo, storyType)
MarkedMessageInfo(threadId, syncMessageId, MessageId(messageId), expirationInfo, storyType, dateReceived)
} else {
null
}
@@ -3005,12 +3006,14 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
}
val dateReceived = editedMessage?.dateReceived ?: System.currentTimeMillis()
val contentValues = ContentValues()
contentValues.put(DATE_SENT, message.sentTimeMillis)
contentValues.put(TYPE, type)
contentValues.put(THREAD_ID, threadId)
contentValues.put(READ, 1)
contentValues.put(DATE_RECEIVED, editedMessage?.dateReceived ?: System.currentTimeMillis())
contentValues.put(DATE_RECEIVED, dateReceived)
contentValues.put(SMS_SUBSCRIPTION_ID, message.subscriptionId)
contentValues.put(EXPIRES_IN, editedMessage?.expiresIn ?: message.expiresIn)
contentValues.put(EXPIRE_TIMER_VERSION, editedMessage?.expireTimerVersion ?: message.expireTimerVersion)
@@ -3136,7 +3139,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
reactions.moveReactionsToNewMessage(messageId, message.messageToEdit)
}
threads.updateLastSeenAndMarkSentAndLastScrolledSilenty(threadId)
threads.updateLastSeenAndMarkSentAndLastScrolledSilenty(threadId, dateReceived)
if (!message.storyType.isStory) {
if (message.outgoingQuote == null && editedMessage == null) {
@@ -4209,11 +4212,11 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
.take(limit.toInt())
}
fun getMessagePositionOnOrAfterTimestamp(threadId: Long, timestamp: Long): Int {
fun getMessagePositionByDateReceivedTimestamp(threadId: Long, timestamp: Long, inclusive: Boolean): Int {
return readableDatabase
.select("COUNT(*)")
.from(TABLE_NAME)
.where("$THREAD_ID = $threadId AND $DATE_RECEIVED >= $timestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL")
.where("$THREAD_ID = $threadId AND $DATE_RECEIVED ${if (inclusive) ">=" else ">"} $timestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL")
.run()
.readToSingleInt()
}
@@ -4593,7 +4596,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
for (threadId in updatedThreads) {
threads.updateReadState(threadId)
threads.setLastSeen(threadId)
}
}
@@ -5004,7 +5006,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
timetamp = this.requireLong(DATE_SENT)
),
expirationInfo = null,
storyType = StoryType.fromCode(this.requireInt(STORY_TYPE))
storyType = StoryType.fromCode(this.requireInt(STORY_TYPE)),
dateReceived = this.requireLong(DATE_RECEIVED)
)
}
@@ -5072,6 +5075,17 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
return !hasMessages
}
fun getMostRecentReadMessageDateReceived(threadId: Long): Long? {
return readableDatabase
.select(DATE_RECEIVED)
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID")
.where("$THREAD_ID = ? AND $READ = 1", threadId)
.orderBy("$DATE_RECEIVED DESC")
.limit(1)
.run()
.readToSingleLongOrNull()
}
fun getMostRecentAddressableMessages(threadId: Long, excludeExpiring: Boolean): Set<MessageRecord> {
return readableDatabase
.select(*MMS_PROJECTION)
@@ -5144,7 +5158,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val syncMessageId: SyncMessageId,
val messageId: MessageId,
val expirationInfo: ExpirationInfo?,
val storyType: StoryType
val storyType: StoryType,
val dateReceived: Long
)
data class InsertResult(

View File

@@ -471,6 +471,23 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
.run()
val messageRecords: List<MarkedMessageInfo> = messages.setAllMessagesRead()
val threadsToMessages = messageRecords.groupBy { it.threadId }
writableDatabase.withinTransaction {
threadsToMessages.forEach { (threadId, messages) ->
val latestDateReceived = messages.maxByOrNull { it.dateReceived }?.dateReceived
if (latestDateReceived != null && latestDateReceived > 0) {
writableDatabase
.update(TABLE_NAME)
.values(
LAST_SCROLLED to 0,
LAST_SEEN to latestDateReceived
)
.where(ID_WHERE, threadId)
.run()
}
}
}
messages.setAllReactionsSeen()
notifyConversationListListeners()
@@ -487,43 +504,43 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
}
fun setEntireThreadRead(threadId: Long): List<MarkedMessageInfo> {
setRead(threadId, false)
setRead(threadId)
return messages.setEntireThreadRead(threadId)
}
fun setRead(threadId: Long, lastSeen: Boolean): List<MarkedMessageInfo> {
return setReadSince(Collections.singletonMap(threadId, -1L), lastSeen)
fun setRead(threadId: Long): List<MarkedMessageInfo> {
return setReadSince(Collections.singletonMap(threadId, -1L))
}
fun setRead(conversationId: ConversationId, lastSeen: Boolean): List<MarkedMessageInfo> {
fun setRead(conversationId: ConversationId): List<MarkedMessageInfo> {
return if (conversationId.groupStoryId == null) {
setRead(conversationId.threadId, lastSeen)
setRead(conversationId.threadId)
} else {
setGroupStoryReadSince(conversationId.threadId, conversationId.groupStoryId, System.currentTimeMillis())
}
}
fun setReadSince(conversationId: ConversationId, lastSeen: Boolean, sinceTimestamp: Long): List<MarkedMessageInfo> {
fun setReadSince(conversationId: ConversationId, sinceTimestamp: Long): List<MarkedMessageInfo> {
return if (conversationId.groupStoryId != null) {
setGroupStoryReadSince(conversationId.threadId, conversationId.groupStoryId, sinceTimestamp)
} else {
setReadSince(conversationId.threadId, lastSeen, sinceTimestamp)
setReadSince(conversationId.threadId, sinceTimestamp)
}
}
fun setReadSince(threadId: Long, lastSeen: Boolean, sinceTimestamp: Long): List<MarkedMessageInfo> {
return setReadSince(Collections.singletonMap(threadId, sinceTimestamp), lastSeen)
fun setReadSince(threadId: Long, sinceTimestamp: Long): List<MarkedMessageInfo> {
return setReadSince(Collections.singletonMap(threadId, sinceTimestamp))
}
fun setRead(threadIds: Collection<Long>, lastSeen: Boolean): List<MarkedMessageInfo> {
return setReadSince(threadIds.associateWith { -1L }, lastSeen)
fun setRead(threadIds: Collection<Long>): List<MarkedMessageInfo> {
return setReadSince(threadIds.associateWith { -1L })
}
private fun setGroupStoryReadSince(threadId: Long, groupStoryId: Long, sinceTimestamp: Long): List<MarkedMessageInfo> {
return messages.setGroupStoryMessagesReadSince(threadId, groupStoryId, sinceTimestamp)
}
fun setReadSince(threadIdToSinceTimestamp: Map<Long, Long>, lastSeen: Boolean): List<MarkedMessageInfo> {
fun setReadSince(threadIdToSinceTimestamp: Map<Long, Long>): List<MarkedMessageInfo> {
val messageRecords: MutableList<MarkedMessageInfo> = LinkedList()
var needsSync = false
@@ -537,17 +554,15 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
val unreadCount = messages.getUnreadCount(threadId)
val unreadMentionsCount = messages.getUnreadMentionCount(threadId)
val lastSeenTimestamp = messages.getMostRecentReadMessageDateReceived(threadId) ?: System.currentTimeMillis()
val contentValues = contentValuesOf(
READ to ReadStatus.READ.serialize(),
UNREAD_COUNT to unreadCount,
UNREAD_SELF_MENTION_COUNT to unreadMentionsCount
UNREAD_SELF_MENTION_COUNT to unreadMentionsCount,
LAST_SEEN to lastSeenTimestamp
)
if (lastSeen) {
contentValues.put(LAST_SEEN, if (sinceTimestamp == -1L) System.currentTimeMillis() else sinceTimestamp)
}
db.update(TABLE_NAME)
.values(contentValues)
.where("$ID = ?", threadId)
@@ -1199,16 +1214,6 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
setArchived(setOf(threadId), archive = false)
}
fun setLastSeen(threadId: Long) {
writableDatabase
.update(TABLE_NAME)
.values(LAST_SEEN to System.currentTimeMillis())
.where("$ID = ?", threadId)
.run()
notifyConversationListListeners()
}
fun setLastScrolled(threadId: Long, lastScrolledTimestamp: Long) {
writableDatabase
.update(TABLE_NAME)
@@ -1488,14 +1493,19 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
.run()
}
fun updateLastSeenAndMarkSentAndLastScrolledSilenty(threadId: Long) {
fun updateLastSeenAndMarkSentAndLastScrolledSilenty(threadId: Long, lastSeenTimestamp: Long) {
val values = contentValuesOf(
HAS_SENT to 1,
LAST_SCROLLED to 0
)
if (lastSeenTimestamp > 0) {
values.put(LAST_SEEN, lastSeenTimestamp)
}
writableDatabase
.update(TABLE_NAME)
.values(
LAST_SEEN to System.currentTimeMillis(),
HAS_SENT to 1,
LAST_SCROLLED to 0
)
.values(values)
.where("$ID = ?", threadId)
.run()
}

View File

@@ -245,7 +245,7 @@ object SyncMessageProcessor {
}
if (threadId != -1L) {
SignalDatabase.threads.setRead(threadId, true)
SignalDatabase.threads.setRead(threadId)
AppDependencies.messageNotifier.updateNotification(context)
}
@@ -959,7 +959,7 @@ object SyncMessageProcessor {
val threadToLatestRead: MutableMap<Long, Long> = HashMap()
val unhandled: Collection<MessageTable.SyncMessageId> = SignalDatabase.messages.setTimestampReadFromSyncMessage(readMessages, envelopeTimestamp, threadToLatestRead)
val markedMessages: List<MarkedMessageInfo> = SignalDatabase.threads.setReadSince(threadToLatestRead, false)
val markedMessages: List<MarkedMessageInfo> = SignalDatabase.threads.setReadSince(threadToLatestRead)
if (Util.hasItems(markedMessages)) {
log("Updating past SignalDatabase.messages: " + markedMessages.size)

View File

@@ -58,7 +58,7 @@ public class MarkReadReceiver extends BroadcastReceiver {
for (ConversationId thread : threads) {
Log.i(TAG, "Marking as read: " + thread);
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(thread, true);
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(thread);
messageIdsCollection.addAll(messageIds);
}

View File

@@ -120,7 +120,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
.addStickyThread(new ConversationId(threadId, groupStoryId != Long.MIN_VALUE ? groupStoryId : null),
intent.getLongExtra(EARLIEST_TIMESTAMP, System.currentTimeMillis()));
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(threadId, true);
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(threadId);
AppDependencies.getMessageNotifier().updateNotification(context);
MarkReadReceiver.process(messageIds);

View File

@@ -159,7 +159,7 @@ public final class MultiShareSender {
}
if (!recipientSearchKey.isStory()) {
SignalDatabase.threads().setRead(threadId, true);
SignalDatabase.threads().setRead(threadId);
}
// XXX We must do this to avoid sending out messages to the same recipient with the same

View File

@@ -94,7 +94,8 @@ class MarkReadReceiverTest {
SyncMessageId(recipientId, 0),
MessageId(1),
ExpirationInfo(0, 0, 0, false),
StoryType.NONE
StoryType.NONE,
0
)
}
}