Add various updates to collapsed events.

This commit is contained in:
Michelle Tang
2026-04-01 10:49:37 -04:00
committed by GitHub
parent dec9eb613e
commit 495e2e043e
8 changed files with 103 additions and 8 deletions

View File

@@ -3,7 +3,9 @@ package org.thoughtcrime.securesms.database
import androidx.core.content.contentValuesOf
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.mockk.every
import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.unmockkObject
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
@@ -298,4 +300,24 @@ class CollapsingMessagesTests {
assertEquals(CollapsedState.COLLAPSED, msgCall4.collapsedState)
assertEquals(call3.messageId, msgCall4.collapsedHeadId)
}
@Test
fun givenMaxCollapsedSet_whenIAddAnotherEvent_thenIExpectANewHead() {
mockkObject(CollapsibleEvents)
every { CollapsibleEvents.MAX_SIZE } returns 2
val messageId1 = message.insertCallLog(alice, MessageTypes.INCOMING_AUDIO_CALL_TYPE, 1000L, false).messageId
val messageId2 = message.insertCallLog(alice, MessageTypes.INCOMING_AUDIO_CALL_TYPE, 2000L, false).messageId
val messageId3 = message.insertCallLog(alice, MessageTypes.INCOMING_AUDIO_CALL_TYPE, 3000L, false).messageId
val msg1 = message.getMessageRecord(messageId1)
val msg2 = message.getMessageRecord(messageId2)
val msg3 = message.getMessageRecord(messageId3)
assertEquals(CollapsedState.HEAD_COLLAPSED, msg1.collapsedState)
assertEquals(CollapsedState.PENDING_COLLAPSED, msg2.collapsedState)
assertEquals(CollapsedState.HEAD_COLLAPSED, msg3.collapsedState)
assertEquals(messageId3, msg3.collapsedHeadId)
unmockkObject(CollapsibleEvents)
}
}

View File

@@ -155,7 +155,7 @@ class ConversationRepository(
metadata.threadSize
)
val config = PagingConfig.Builder().setPageSize(25)
.setBufferPages(2)
.setBufferPages(3)
.setStartIndex(max(metadata.getStartPosition(), 0))
.build()

View File

@@ -7,6 +7,8 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
*/
object CollapsibleEvents {
const val MAX_SIZE = 50
@JvmStatic
fun isCollapsibleType(type: Long, messageExtras: MessageExtras?): Boolean {
return getCollapsibleType(type, messageExtras) != null
@@ -41,6 +43,18 @@ object CollapsibleEvents {
return CollapsibleType.CHAT_UPDATE
}
if (MessageTypes.isPinnedMessageUpdate(type)) {
return CollapsibleType.CHAT_UPDATE
}
if (MessageTypes.isPollTerminate(type)) {
return CollapsibleType.CHAT_UPDATE
}
if (MessageTypes.isChangeNumber(type)) {
return CollapsibleType.CHAT_UPDATE
}
return null
}

View File

@@ -3631,12 +3631,29 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
.limit(1)
.run()
.readToSingleObject { cursor ->
PotentialCollapsibleMessage(
val message = PotentialCollapsibleMessage(
type = cursor.requireLong(TYPE),
dateReceived = cursor.requireLong(DATE_RECEIVED),
collapsedHeadId = cursor.requireLong(COLLAPSED_HEAD_ID),
messageExtras = cursor.requireBlob(MESSAGE_EXTRAS)?.let { MessageExtras.ADAPTER.decode(it) }
)
val collapsedSize = if (message.collapsedHeadId != 0L) {
readableDatabase
.count()
.from(TABLE_NAME)
.where("$COLLAPSED_HEAD_ID = ?", message.collapsedHeadId)
.run()
.readToSingleInt()
} else {
-1
}
if (collapsedSize in 1..<CollapsibleEvents.MAX_SIZE) {
message
} else {
null
}
}?.takeIf { DateUtils.isSameDay(it.dateReceived, dateReceived) }
}

View File

@@ -82,11 +82,11 @@ class BackfillCollapsedMessageJob private constructor(
} else {
val previous = lastMessageByThread[message.threadId]
val (collapsedState, headId) = if ((previous?.collapsibleType == collapsibleType) && DateUtils.isSameDay(previous.dateReceived, message.dateReceived)) {
val (collapsedState, headId, size) = if ((previous?.collapsibleType == collapsibleType) && DateUtils.isSameDay(previous.dateReceived, message.dateReceived) && previous.collapsedSetSize < CollapsibleEvents.MAX_SIZE) {
val state = if (message.read) CollapsedState.COLLAPSED.id else CollapsedState.PENDING_COLLAPSED.id
Pair(state, previous.headId)
Triple(state, previous.headId, previous.collapsedSetSize)
} else {
Pair(CollapsedState.HEAD_COLLAPSED.id, message.id)
Triple(CollapsedState.HEAD_COLLAPSED.id, message.id, 0)
}
db.update(MessageTable.TABLE_NAME)
@@ -96,7 +96,7 @@ class BackfillCollapsedMessageJob private constructor(
)
.where("${MessageTable.ID} = ?", message.id)
.run()
lastMessageByThread[message.threadId] = LastMessage(collapsibleType, headId, message.dateReceived)
lastMessageByThread[message.threadId] = LastMessage(collapsibleType, headId, message.dateReceived, size + 1)
}
}
@@ -134,7 +134,8 @@ class BackfillCollapsedMessageJob private constructor(
private data class LastMessage(
val collapsibleType: CollapsibleEvents.CollapsibleType?,
val headId: Long,
val dateReceived: Long
val dateReceived: Long,
val collapsedSetSize: Int
)
class Factory : Job.Factory<BackfillCollapsedMessageJob> {

View File

@@ -57,6 +57,7 @@ import org.thoughtcrime.securesms.migrations.AttributesMigrationJob;
import org.thoughtcrime.securesms.migrations.AvatarColorStorageServiceMigrationJob;
import org.thoughtcrime.securesms.migrations.AvatarIdRemovalMigrationJob;
import org.thoughtcrime.securesms.migrations.AvatarMigrationJob;
import org.thoughtcrime.securesms.migrations.BackfillCollapsedEventsMigrationJob;
import org.thoughtcrime.securesms.migrations.BackfillDigestsForDuplicatesMigrationJob;
import org.thoughtcrime.securesms.migrations.BackupJitterMigrationJob;
import org.thoughtcrime.securesms.migrations.BackupNotificationMigrationJob;
@@ -313,6 +314,7 @@ public final class JobManagerFactories {
put(AvatarColorStorageServiceMigrationJob.KEY, new AvatarColorStorageServiceMigrationJob.Factory());
put(AvatarIdRemovalMigrationJob.KEY, new AvatarIdRemovalMigrationJob.Factory());
put(AvatarMigrationJob.KEY, new AvatarMigrationJob.Factory());
put(BackfillCollapsedEventsMigrationJob.KEY, new BackfillCollapsedEventsMigrationJob.Factory());
put(BackfillDigestsForDuplicatesMigrationJob.KEY, new BackfillDigestsForDuplicatesMigrationJob.Factory());
put(BackupJitterMigrationJob.KEY, new BackupJitterMigrationJob.Factory());
put(BackupNotificationMigrationJob.KEY, new BackupNotificationMigrationJob.Factory());

View File

@@ -196,9 +196,10 @@ public class ApplicationMigrations {
static final int DELETED_BY_DB_MIGRATION = 152;
static final int RELEASE_CHANNEL_RECIPIENT_FIX = 153;
static final int EMOJI_VERSION_13 = 154;
static final int COLLAPSED_EVENTS = 155;
}
public static final int CURRENT_VERSION = 154;
public static final int CURRENT_VERSION = 155;
/**
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
@@ -909,6 +910,10 @@ public class ApplicationMigrations {
jobs.put(Version.EMOJI_VERSION_13, new EmojiDownloadMigrationJob());
}
if (lastSeenVersion < Version.COLLAPSED_EVENTS) {
jobs.put(Version.COLLAPSED_EVENTS, new BackfillCollapsedEventsMigrationJob());
}
return jobs;
}

View File

@@ -0,0 +1,34 @@
package org.thoughtcrime.securesms.migrations
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobs.BackfillCollapsedMessageJob
/**
* Schedules the [BackfillCollapsedMessageJob] to run.
*/
internal class BackfillCollapsedEventsMigrationJob private constructor(parameters: Parameters) : MigrationJob(parameters) {
companion object {
const val KEY = "BackfillCollapsedEventsMigrationJob"
}
internal constructor() : this(Parameters.Builder().build())
override fun isUiBlocking(): Boolean = false
override fun getFactoryKey(): String = KEY
override fun performMigration() {
val jobManager = AppDependencies.jobManager
jobManager.add(BackfillCollapsedMessageJob())
}
override fun shouldRetry(e: Exception): Boolean = false
class Factory : Job.Factory<BackfillCollapsedEventsMigrationJob> {
override fun create(parameters: Parameters, serializedData: ByteArray?): BackfillCollapsedEventsMigrationJob {
return BackfillCollapsedEventsMigrationJob(parameters)
}
}
}