mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-09-13 20:17:56 +01:00
Fix read syncs not marking older messages read after being offline.
This commit is contained in:
committed by
Cody Henthorne
parent
3c8aab9be1
commit
50ec14fb28
@@ -5720,7 +5720,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val threads: MutableList<Long> = LinkedList()
|
||||
|
||||
readableDatabase
|
||||
.select(ID, TYPE, THREAD_ID, EXPIRES_IN, EXPIRE_STARTED, LATEST_REVISION_ID)
|
||||
.select(ID, TYPE, THREAD_ID, DATE_RECEIVED, EXPIRES_IN, EXPIRE_STARTED, LATEST_REVISION_ID)
|
||||
.from(TABLE_NAME)
|
||||
.where("$DATE_SENT = ? AND ($FROM_RECIPIENT_ID = ? OR ($FROM_RECIPIENT_ID = ? AND $outgoingTypeClause))", messageId.timetamp, messageId.recipientId, Recipient.self().id)
|
||||
.run()
|
||||
@@ -5728,6 +5728,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val id = cursor.requireLong(ID)
|
||||
val type = cursor.requireLong(TYPE)
|
||||
val threadId = cursor.requireLong(THREAD_ID)
|
||||
val dateReceived = cursor.requireLong(DATE_RECEIVED)
|
||||
val expiresIn = cursor.requireLong(EXPIRES_IN)
|
||||
val expireStarted = cursor.requireLong(EXPIRE_STARTED).let {
|
||||
if (it > 0) {
|
||||
@@ -5763,9 +5764,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val latest: Long? = threadToLatestRead[threadId]
|
||||
|
||||
threadToLatestRead[threadId] = if (latest != null) {
|
||||
max(latest, messageId.timetamp)
|
||||
max(latest, dateReceived)
|
||||
} else {
|
||||
messageId.timetamp
|
||||
dateReceived
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.app.Application
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.thoughtcrime.securesms.mms.IncomingMessage
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.testutil.RecipientTestRule
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage
|
||||
|
||||
/**
|
||||
* Verifies that read syncs from a linked device mark the whole thread read up to the synced message, including when
|
||||
* the messages were received long after they were sent (e.g. a backlog drained after the device was offline).
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE, application = Application::class)
|
||||
class MessageTableTest_readSyncTimestamps {
|
||||
|
||||
@get:Rule
|
||||
val recipients = RecipientTestRule()
|
||||
|
||||
private val messages: MessageTable
|
||||
get() = SignalDatabase.messages
|
||||
|
||||
private lateinit var senderId: RecipientId
|
||||
private var threadId: Long = 0
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
senderId = recipients.createRecipient("Sender Name")
|
||||
threadId = SignalDatabase.threads.getOrCreateThreadIdFor(senderId, false, ThreadTable.DistributionTypes.DEFAULT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun latestReadIsTrackedByDateReceived() {
|
||||
insertIncoming(sentTime = 1000, receivedTime = 5000)
|
||||
insertIncoming(sentTime = 1001, receivedTime = 5001)
|
||||
|
||||
val threadToLatestRead: MutableMap<Long, Long> = mutableMapOf()
|
||||
messages.setTimestampReadFromSyncMessage(listOf(readSync(1001)), 1001, threadToLatestRead)
|
||||
|
||||
assertThat(threadToLatestRead[threadId]).isEqualTo(5001)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun readSyncMarksOlderMessagesReadWhenReceivedLongAfterBeingSent() {
|
||||
insertIncoming(sentTime = 1000, receivedTime = 5000)
|
||||
insertIncoming(sentTime = 1001, receivedTime = 5001)
|
||||
insertIncoming(sentTime = 1002, receivedTime = 5002)
|
||||
|
||||
assertThat(messages.getUnreadCount(threadId)).isEqualTo(3)
|
||||
|
||||
// A linked device only tells us about the newest message it read. Everything older in the thread is implied.
|
||||
syncRead(1002)
|
||||
|
||||
assertThat(messages.getUnreadCount(threadId)).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun readSyncLeavesNewerMessagesUnread() {
|
||||
insertIncoming(sentTime = 1000, receivedTime = 5000)
|
||||
insertIncoming(sentTime = 1001, receivedTime = 5001)
|
||||
insertIncoming(sentTime = 1002, receivedTime = 5002)
|
||||
|
||||
syncRead(1001)
|
||||
|
||||
assertThat(messages.getUnreadCount(threadId)).isEqualTo(1)
|
||||
assertThat(messages.getOldestUnread(threadId)?.dateReceived).isEqualTo(5002)
|
||||
}
|
||||
|
||||
// region helpers
|
||||
|
||||
private fun insertIncoming(sentTime: Long, receivedTime: Long): Long {
|
||||
val message = IncomingMessage(
|
||||
type = MessageType.NORMAL,
|
||||
from = senderId,
|
||||
sentTimeMillis = sentTime,
|
||||
serverTimeMillis = sentTime,
|
||||
receivedTimeMillis = receivedTime,
|
||||
body = "msg $sentTime"
|
||||
)
|
||||
return messages.insertMessageInbox(message, threadId).get().messageId
|
||||
}
|
||||
|
||||
private fun syncRead(sentTime: Long) {
|
||||
val threadToLatestRead: MutableMap<Long, Long> = mutableMapOf()
|
||||
messages.setTimestampReadFromSyncMessage(listOf(readSync(sentTime)), sentTime, threadToLatestRead)
|
||||
SignalDatabase.threads.setReadSince(threadToLatestRead)
|
||||
}
|
||||
|
||||
private fun readSync(sentTime: Long): SyncMessage.Read {
|
||||
return SyncMessage.Read(
|
||||
senderAci = Recipient.resolved(senderId).requireAci().toString(),
|
||||
timestamp = sentTime
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
Reference in New Issue
Block a user