diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/CollapsingMessagesTests.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/CollapsingMessagesTests.kt index 14a8e5ae2c..74a4ce8ccb 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/database/CollapsingMessagesTests.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/CollapsingMessagesTests.kt @@ -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) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt index de4c343c32..de8826ca10 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt @@ -155,7 +155,7 @@ class ConversationRepository( metadata.threadSize ) val config = PagingConfig.Builder().setPageSize(25) - .setBufferPages(2) + .setBufferPages(3) .setStartIndex(max(metadata.getStartPosition(), 0)) .build() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/CollapsibleEvents.kt b/app/src/main/java/org/thoughtcrime/securesms/database/CollapsibleEvents.kt index f552e7fa9b..64171189ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/CollapsibleEvents.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/CollapsibleEvents.kt @@ -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 } 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 5377846e92..4053da53f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.kt @@ -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.. { 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 0165825524..6bf3b53ebe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -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()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java index 4f9715b9ea..79e72d6c9a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -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; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/BackfillCollapsedEventsMigrationJob.kt b/app/src/main/java/org/thoughtcrime/securesms/migrations/BackfillCollapsedEventsMigrationJob.kt new file mode 100644 index 0000000000..bb67390472 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/BackfillCollapsedEventsMigrationJob.kt @@ -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 { + override fun create(parameters: Parameters, serializedData: ByteArray?): BackfillCollapsedEventsMigrationJob { + return BackfillCollapsedEventsMigrationJob(parameters) + } + } +}