diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index b0b653d08..f4d066945 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -48,8 +48,6 @@
-
-
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/SafetyNumberChangeDialogPreviewer.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/SafetyNumberChangeDialogPreviewer.kt
index 5a544664a..7adcb2b95 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/SafetyNumberChangeDialogPreviewer.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/SafetyNumberChangeDialogPreviewer.kt
@@ -64,7 +64,7 @@ class SafetyNumberChangeDialogPreviewer {
SafetyNumberBottomSheet
.forIdentityRecordsAndDestinations(
identityRecords = ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecords(othersRecipients).identityRecords,
- destinations = listOf(ContactSearchKey.RecipientSearchKey.Story(myStoryRecipientId))
+ destinations = listOf(ContactSearchKey.RecipientSearchKey(myStoryRecipientId, true))
)
.show(conversationActivity.supportFragmentManager)
}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt
new file mode 100644
index 000000000..7f32ecf0c
--- /dev/null
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/database/GroupTableTest.kt
@@ -0,0 +1,213 @@
+package org.thoughtcrime.securesms.database
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.signal.core.util.delete
+import org.signal.core.util.readToList
+import org.signal.core.util.requireLong
+import org.signal.core.util.withinTransaction
+import org.signal.libsignal.zkgroup.groups.GroupMasterKey
+import org.signal.storageservice.protos.groups.Member
+import org.signal.storageservice.protos.groups.local.DecryptedGroup
+import org.signal.storageservice.protos.groups.local.DecryptedMember
+import org.thoughtcrime.securesms.groups.GroupId
+import org.thoughtcrime.securesms.recipients.Recipient
+import org.thoughtcrime.securesms.recipients.RecipientId
+import org.thoughtcrime.securesms.testing.SignalActivityRule
+import java.security.SecureRandom
+import kotlin.random.Random
+
+class GroupTableTest {
+
+ @get:Rule
+ val harness = SignalActivityRule()
+
+ private lateinit var groupTable: GroupTable
+
+ @Before
+ fun setUp() {
+ groupTable = SignalDatabase.groups
+
+ groupTable.writableDatabase.delete(GroupTable.TABLE_NAME).run()
+ groupTable.writableDatabase.delete(GroupTable.MembershipTable.TABLE_NAME).run()
+ }
+
+ @Test
+ fun whenICreateGroupV2_thenIExpectMemberRowsPopulated() {
+ val groupId = insertPushGroup()
+
+ //language=sql
+ val members: List = groupTable.writableDatabase.query(
+ """
+ SELECT ${GroupTable.MembershipTable.RECIPIENT_ID}
+ FROM ${GroupTable.MembershipTable.TABLE_NAME}
+ WHERE ${GroupTable.MembershipTable.GROUP_ID} = "${groupId.serialize()}"
+ """.trimIndent()
+ ).readToList {
+ RecipientId.from(it.requireLong(GroupTable.RECIPIENT_ID))
+ }
+
+ assertEquals(2, members.size)
+ }
+
+ @Test
+ fun givenAGroupV2_whenIGetGroupsContainingMember_thenIExpectGroup() {
+ val groupId = insertPushGroup()
+ insertThread(groupId)
+
+ val groups = groupTable.getGroupsContainingMember(harness.others[0], false)
+
+ assertEquals(1, groups.size)
+ assertEquals(groupId, groups[0].id)
+ }
+
+ @Test
+ fun givenAnMmsGroup_whenIGetMembers_thenIExpectAllMembers() {
+ val groupId = insertMmsGroup()
+
+ val groups = groupTable.getGroupMemberIds(groupId, GroupTable.MemberSet.FULL_MEMBERS_INCLUDING_SELF)
+
+ assertEquals(2, groups.size)
+ }
+
+ @Test
+ fun givenGroups_whenIQueryGroupsByMembership_thenIExpectBothGroups() {
+ insertPushGroup()
+ insertMmsGroup(members = listOf(harness.others[1]))
+
+ val groups = groupTable.queryGroupsByMembership(
+ setOf(harness.self.id, harness.others[1]),
+ includeInactive = false,
+ excludeV1 = false,
+ excludeMms = false
+ )
+
+ assertEquals(2, groups.cursor?.count)
+ }
+
+ @Test
+ fun givenGroups_whenIGetGroups_thenIExpectBothGroups() {
+ insertPushGroup()
+ insertMmsGroup(members = listOf(harness.others[1]))
+
+ val groups = groupTable.getGroups()
+
+ assertEquals(2, groups.cursor?.count)
+ }
+
+ @Test
+ fun givenAGroup_whenIGetGroup_thenIExpectGroup() {
+ val v2Group = insertPushGroup()
+ insertThread(v2Group)
+
+ val groupRecord = groupTable.getGroup(v2Group).get()
+ assertEquals(setOf(harness.self.id, harness.others[0]), groupRecord.members.toSet())
+ }
+
+ @Test
+ fun givenAGroupAndARemap_whenIGetGroup_thenIExpectRemap() {
+ val v2Group = insertPushGroup()
+ insertThread(v2Group)
+
+ groupTable.writableDatabase.withinTransaction {
+ RemappedRecords.getInstance().addRecipient(harness.others[0], harness.others[1])
+ }
+
+ val groupRecord = groupTable.getGroup(v2Group).get()
+ assertEquals(groupRecord.members.toSet(), setOf(harness.self.id, harness.others[1]))
+ }
+
+ @Test
+ fun givenAGroupAndMember_whenIIsCurrentMember_thenIExpectTrue() {
+ val v2Group = insertPushGroup()
+
+ val actual = groupTable.isCurrentMember(v2Group.requirePush(), harness.others[0])
+
+ assertTrue(actual)
+ }
+
+ @Test
+ fun givenAGroupAndMember_whenIRemove_thenIExpectNotAMember() {
+ val v2Group = insertPushGroup()
+
+ groupTable.remove(v2Group, harness.others[0])
+ val actual = groupTable.isCurrentMember(v2Group.requirePush(), harness.others[0])
+
+ assertFalse(actual)
+ }
+
+ @Test
+ fun givenAGroupAndNonMember_whenIIsCurrentMember_thenIExpectFalse() {
+ val v2Group = insertPushGroup()
+
+ val actual = groupTable.isCurrentMember(v2Group.requirePush(), harness.others[1])
+
+ assertFalse(actual)
+ }
+
+ @Test
+ fun givenAGroup_whenIUpdateMembers_thenIExpectUpdatedMembers() {
+ val v2Group = insertPushGroup()
+ groupTable.updateMembers(v2Group, listOf(harness.self.id, harness.others[1]))
+ val groupRecord = groupTable.getGroup(v2Group)
+
+ assertEquals(setOf(harness.self.id, harness.others[1]), groupRecord.get().members.toSet())
+ }
+
+ @Test
+ fun givenAnMmsGroup_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() {
+ val members: List = listOf(harness.self.id, harness.others[0])
+ val other = insertMmsGroup(members + listOf(harness.others[1]))
+ val mmsGroup = insertMmsGroup(members)
+ val actual = groupTable.getOrCreateMmsGroupForMembers(members.toSet())
+
+ assertNotEquals(other, actual)
+ assertEquals(mmsGroup, actual)
+ }
+
+ private fun insertThread(groupId: GroupId): Long {
+ val groupRecipient = SignalDatabase.recipients.getByGroupId(groupId).get()
+ return SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(groupRecipient))
+ }
+
+ private fun insertMmsGroup(members: List = listOf(harness.self.id, harness.others[0])): GroupId {
+ val id = GroupId.createMms(SecureRandom())
+ groupTable.create(
+ id,
+ null,
+ members.apply {
+ println("Creating a group with ${members.size} members")
+ }
+ )
+
+ return id
+ }
+
+ private fun insertPushGroup(): GroupId {
+ val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
+ val decryptedGroupState = DecryptedGroup.newBuilder()
+ .addAllMembers(
+ listOf(
+ DecryptedMember.newBuilder()
+ .setUuid(harness.self.requireServiceId().toByteString())
+ .setJoinedAtRevision(0)
+ .setRole(Member.Role.DEFAULT)
+ .build(),
+ DecryptedMember.newBuilder()
+ .setUuid(Recipient.resolved(harness.others[0]).requireServiceId().toByteString())
+ .setJoinedAtRevision(0)
+ .setRole(Member.Role.DEFAULT)
+ .build()
+ )
+ )
+ .setRevision(0)
+ .build()
+
+ return groupTable.create(groupMasterKey, decryptedGroupState)
+ }
+}
diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/safety/SafetyNumberBottomSheetRepositoryTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/safety/SafetyNumberBottomSheetRepositoryTest.kt
index 1f9f17213..ca87741ce 100644
--- a/app/src/androidTest/java/org/thoughtcrime/securesms/safety/SafetyNumberBottomSheetRepositoryTest.kt
+++ b/app/src/androidTest/java/org/thoughtcrime/securesms/safety/SafetyNumberBottomSheetRepositoryTest.kt
@@ -27,7 +27,7 @@ class SafetyNumberBottomSheetRepositoryTest {
@Test
fun givenIOnlyHave1to1Destinations_whenIGetBuckets_thenIOnlyHaveContactsBucketContainingAllRecipients() {
val recipients = harness.others
- val destinations = harness.others.map { ContactSearchKey.RecipientSearchKey.KnownRecipient(it) }
+ val destinations = harness.others.map { ContactSearchKey.RecipientSearchKey(it, false) }
val result = subjectUnderTest.getBuckets(recipients, destinations).test()
@@ -42,7 +42,7 @@ class SafetyNumberBottomSheetRepositoryTest {
fun givenIOnlyHaveASingle1to1Destination_whenIGetBuckets_thenIOnlyHaveContactsBucketContainingAllRecipients() {
// GIVEN
val recipients = harness.others
- val destination = harness.others.take(1).map { ContactSearchKey.RecipientSearchKey.KnownRecipient(it) }
+ val destination = harness.others.take(1).map { ContactSearchKey.RecipientSearchKey(it, false) }
// WHEN
val result = subjectUnderTest.getBuckets(recipients, destination).test(1)
@@ -59,7 +59,7 @@ class SafetyNumberBottomSheetRepositoryTest {
// GIVEN
val distributionListMembers = harness.others.take(5)
val distributionList = SignalDatabase.distributionLists.createList("ListA", distributionListMembers)!!
- val destinationKey = ContactSearchKey.RecipientSearchKey.Story(SignalDatabase.distributionLists.getRecipientId(distributionList)!!)
+ val destinationKey = ContactSearchKey.RecipientSearchKey(SignalDatabase.distributionLists.getRecipientId(distributionList)!!, true)
// WHEN
val result = subjectUnderTest.getBuckets(harness.others, listOf(destinationKey)).test(1)
@@ -82,7 +82,7 @@ class SafetyNumberBottomSheetRepositoryTest {
val distributionListMembers = harness.others.take(5)
val toRemove = distributionListMembers.last()
val distributionList = SignalDatabase.distributionLists.createList("ListA", distributionListMembers)!!
- val destinationKey = ContactSearchKey.RecipientSearchKey.Story(SignalDatabase.distributionLists.getRecipientId(distributionList)!!)
+ val destinationKey = ContactSearchKey.RecipientSearchKey(SignalDatabase.distributionLists.getRecipientId(distributionList)!!, true)
val testSubscriber = subjectUnderTest.getBuckets(distributionListMembers, listOf(destinationKey)).test(2)
testScheduler.triggerActions()
@@ -108,7 +108,7 @@ class SafetyNumberBottomSheetRepositoryTest {
// GIVEN
val distributionListMembers = harness.others.take(5)
val distributionList = SignalDatabase.distributionLists.createList("ListA", distributionListMembers)!!
- val destinationKey = ContactSearchKey.RecipientSearchKey.Story(SignalDatabase.distributionLists.getRecipientId(distributionList)!!)
+ val destinationKey = ContactSearchKey.RecipientSearchKey(SignalDatabase.distributionLists.getRecipientId(distributionList)!!, true)
val testSubscriber = subjectUnderTest.getBuckets(distributionListMembers, listOf(destinationKey)).test(2)
testScheduler.triggerActions()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt
index 9901c9abd..f5576f751 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupTable.kt
@@ -13,6 +13,7 @@ import org.signal.core.util.SqlUtil.appendArg
import org.signal.core.util.SqlUtil.buildArgs
import org.signal.core.util.SqlUtil.buildCaseInsensitiveGlobPattern
import org.signal.core.util.SqlUtil.buildCollectionQuery
+import org.signal.core.util.delete
import org.signal.core.util.exists
import org.signal.core.util.isAbsent
import org.signal.core.util.logging.Log
@@ -50,7 +51,6 @@ import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
-import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil
import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
@@ -58,11 +58,7 @@ import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.util.UuidUtil
import java.io.Closeable
-import java.lang.AssertionError
-import java.lang.IllegalArgumentException
-import java.lang.IllegalStateException
import java.security.SecureRandom
-import java.util.ArrayList
import java.util.Optional
import java.util.UUID
import java.util.stream.Collectors
@@ -72,12 +68,13 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
companion object {
private val TAG = Log.tag(GroupTable::class.java)
+ const val MEMBER_GROUP_CONCAT = "member_group_concat"
+
const val TABLE_NAME = "groups"
const val ID = "_id"
const val GROUP_ID = "group_id"
const val RECIPIENT_ID = "recipient_id"
const val TITLE = "title"
- const val MEMBERS = "members"
const val AVATAR_ID = "avatar_id"
const val AVATAR_KEY = "avatar_key"
const val AVATAR_CONTENT_TYPE = "avatar_content_type"
@@ -112,7 +109,6 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
$GROUP_ID TEXT,
$RECIPIENT_ID INTEGER,
$TITLE TEXT,
- $MEMBERS TEXT,
$AVATAR_ID INTEGER,
$AVATAR_KEY BLOB,
$AVATAR_CONTENT_TYPE TEXT,
@@ -145,7 +141,6 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
GROUP_ID,
RECIPIENT_ID,
TITLE,
- MEMBERS,
UNMIGRATED_V1_MEMBERS,
AVATAR_ID,
AVATAR_KEY,
@@ -165,43 +160,77 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
.filterNot { it == RECIPIENT_ID }
.map { columnName: String -> "$TABLE_NAME.$columnName" }
.toList()
+
+ //language=sql
+ private val JOINED_GROUP_SELECT = """
+ SELECT
+ DISTINCT $TABLE_NAME.*,
+ GROUP_CONCAT(${MembershipTable.TABLE_NAME}.${MembershipTable.RECIPIENT_ID}) as $MEMBER_GROUP_CONCAT
+ FROM $TABLE_NAME
+ INNER JOIN ${MembershipTable.TABLE_NAME} ON ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID} = $TABLE_NAME.$GROUP_ID
+ """.toSingleLine()
+
+ val CREATE_TABLES = arrayOf(CREATE_TABLE, MembershipTable.CREATE_TABLE)
+ }
+
+ class MembershipTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper) {
+ companion object {
+ const val TABLE_NAME = "group_membership"
+
+ const val ID = "_id"
+ const val GROUP_ID = "group_id"
+ const val RECIPIENT_ID = "recipient_id"
+
+ //language=sql
+ @JvmField
+ val CREATE_TABLE = """
+ CREATE TABLE $TABLE_NAME (
+ $ID INTEGER PRIMARY KEY,
+ $GROUP_ID TEXT NOT NULL,
+ $RECIPIENT_ID INTEGER NOT NULL,
+ UNIQUE($GROUP_ID, $RECIPIENT_ID)
+ )
+ """.toSingleLine()
+ }
}
fun getGroup(recipientId: RecipientId): Optional {
- readableDatabase
- .select()
- .from(TABLE_NAME)
- .where("$RECIPIENT_ID = ?", recipientId)
- .run()
- .use { cursor ->
- return if (cursor.moveToFirst()) {
- getGroup(cursor)
- } else {
- Optional.empty()
- }
- }
+ return getGroup(SqlUtil.Query("$TABLE_NAME.$RECIPIENT_ID = ?", buildArgs(recipientId)))
}
fun getGroup(groupId: GroupId): Optional {
+ return getGroup(SqlUtil.Query("$TABLE_NAME.$GROUP_ID = ?", buildArgs(groupId)))
+ }
+
+ private fun getGroup(query: SqlUtil.Query): Optional {
+ //language=sql
+ val select = "$JOINED_GROUP_SELECT WHERE ${query.where} GROUP BY ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID}"
+
readableDatabase
- .select()
- .from(TABLE_NAME)
- .where("$GROUP_ID = ?", groupId.toString())
- .run()
+ .query(select, query.whereArgs)
.use { cursor ->
return if (cursor.moveToFirst()) {
val groupRecord = getGroup(cursor)
if (groupRecord.isPresent && RemappedRecords.getInstance().areAnyRemapped(groupRecord.get().members)) {
+ val groupId = groupRecord.get().id
val remaps = RemappedRecords.getInstance().buildRemapDescription(groupRecord.get().members)
Log.w(TAG, "Found a group with remapped recipients in it's membership list! Updating the list. GroupId: $groupId, Remaps: $remaps", true)
- val remapped: Collection = RemappedRecords.getInstance().remap(groupRecord.get().members)
+ val oldToNew: List> = groupRecord.get().members.map {
+ it to RemappedRecords.getInstance().getRecipient(it).orElse(null)
+ }.filterNot { (old, new) -> new == null || old == new }
- val updateCount = writableDatabase
- .update(TABLE_NAME)
- .values(MEMBERS to remapped.serialize())
- .where("$GROUP_ID = ?", groupId)
- .run()
+ var updateCount = 0
+ if (oldToNew.isNotEmpty()) {
+ writableDatabase.withinTransaction { db ->
+ for ((old, new) in oldToNew) {
+ updateCount += db.update(MembershipTable.TABLE_NAME)
+ .values(MembershipTable.RECIPIENT_ID to new!!.serialize())
+ .where("${MembershipTable.GROUP_ID} = ? AND ${MembershipTable.RECIPIENT_ID} = ?", groupId, old)
+ .run()
+ }
+ }
+ }
if (updateCount > 0) {
getGroup(groupId)
@@ -240,33 +269,11 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
* @return A gv1 group whose expected v2 ID matches the one provided.
*/
fun getGroupV1ByExpectedV2(gv2Id: GroupId.V2): Optional {
- readableDatabase
- .select(*GROUP_PROJECTION)
- .from(TABLE_NAME)
- .where("$EXPECTED_V2_ID = ?", gv2Id)
- .run()
- .use { cursor ->
- return if (cursor.moveToFirst()) {
- getGroup(cursor)
- } else {
- Optional.empty()
- }
- }
+ return getGroup(SqlUtil.Query("$TABLE_NAME.$EXPECTED_V2_ID = ?", buildArgs(gv2Id)))
}
fun getGroupByDistributionId(distributionId: DistributionId): Optional {
- readableDatabase
- .select()
- .from(TABLE_NAME)
- .where("$DISTRIBUTION_ID = ?", distributionId)
- .run()
- .use { cursor ->
- return if (cursor.moveToFirst()) {
- getGroup(cursor)
- } else {
- Optional.empty()
- }
- }
+ return getGroup(SqlUtil.Query("$TABLE_NAME.$DISTRIBUTION_ID = ?", buildArgs(distributionId)))
}
fun removeUnmigratedV1Members(id: GroupId.V2) {
@@ -338,7 +345,14 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
fun queryGroupsByTitle(inputQuery: String, includeInactive: Boolean, excludeV1: Boolean, excludeMms: Boolean): Reader {
val query = getGroupQueryWhereStatement(inputQuery, includeInactive, excludeV1, excludeMms)
- val cursor = databaseHelper.signalReadableDatabase.query(TABLE_NAME, null, query.where, query.whereArgs, null, null, "$TITLE COLLATE NOCASE ASC")
+ val statement = """
+ $JOINED_GROUP_SELECT
+ WHERE ${query.where}
+ GROUP BY ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID}
+ ORDER BY $TITLE COLLATE NOCASE ASC
+ """.trimIndent()
+
+ val cursor = databaseHelper.signalReadableDatabase.query(statement, query.whereArgs)
return Reader(cursor)
}
@@ -353,21 +367,17 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
recipientIds = recipientIds.take(30).toSet()
}
- val recipientLikeClauses = recipientIds
- .map { it.toLong() }
- .map { id -> "($MEMBERS LIKE $id || ',%' OR $MEMBERS LIKE '%,' || $id || ',%' OR $MEMBERS LIKE '%,' || $id)" }
- .toList()
+ val membershipQuery = SqlUtil.buildSingleCollectionQuery("${MembershipTable.TABLE_NAME}.${MembershipTable.RECIPIENT_ID}", recipientIds)
var query: String
val queryArgs: Array
- val membershipQuery = "(" + Util.join(recipientLikeClauses, " OR ") + ")"
if (includeInactive) {
- query = "$membershipQuery AND ($ACTIVE = ? OR $RECIPIENT_ID IN (SELECT ${ThreadTable.RECIPIENT_ID} FROM ${ThreadTable.TABLE_NAME}))"
- queryArgs = buildArgs(1)
+ query = "${membershipQuery.where} AND ($ACTIVE = ? OR $TABLE_NAME.$RECIPIENT_ID IN (SELECT ${ThreadTable.RECIPIENT_ID} FROM ${ThreadTable.TABLE_NAME}))"
+ queryArgs = membershipQuery.whereArgs + buildArgs(1)
} else {
- query = "$membershipQuery AND $ACTIVE = ?"
- queryArgs = buildArgs(1)
+ query = "${membershipQuery.where} AND $ACTIVE = ?"
+ queryArgs = membershipQuery.whereArgs + buildArgs(1)
}
if (excludeV1) {
@@ -378,15 +388,15 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
query += " AND $MMS = 0"
}
- return Reader(readableDatabase.query(TABLE_NAME, null, query, queryArgs, null, null, null))
+ return Reader(readableDatabase.query("$JOINED_GROUP_SELECT WHERE $query GROUP BY ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID}", queryArgs))
}
private fun queryGroupsByRecency(groupQuery: GroupQuery): Reader {
val query = getGroupQueryWhereStatement(groupQuery.searchQuery, groupQuery.includeInactive, !groupQuery.includeV1, !groupQuery.includeMms)
val sql = """
- SELECT $TABLE_NAME.*
- FROM $TABLE_NAME LEFT JOIN ${ThreadTable.TABLE_NAME} ON $TABLE_NAME.$RECIPIENT_ID = ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID}
+ $JOINED_GROUP_SELECT
WHERE ${query.where}
+ ${"GROUP BY ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID}"}
ORDER BY ${ThreadTable.TABLE_NAME}.${ThreadTable.DATE} DESC
""".toSingleLine()
@@ -407,7 +417,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
val caseInsensitiveQuery = buildCaseInsensitiveGlobPattern(inputQuery)
if (includeInactive) {
- query = "$TITLE GLOB ? AND ($ACTIVE = ? OR $RECIPIENT_ID IN (SELECT ${ThreadTable.RECIPIENT_ID} FROM ${ThreadTable.TABLE_NAME}))"
+ query = "$TITLE GLOB ? AND ($ACTIVE = ? OR $TABLE_NAME.$RECIPIENT_ID IN (SELECT ${ThreadTable.RECIPIENT_ID} FROM ${ThreadTable.TABLE_NAME}))"
queryArgs = buildArgs(caseInsensitiveQuery, 1)
} else {
query = "$TITLE GLOB ? AND $ACTIVE = ?"
@@ -456,23 +466,27 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
}
}
- fun getOrCreateMmsGroupForMembers(members: List): GroupId.Mms {
- val sortedMembers = members.sorted()
+ fun getOrCreateMmsGroupForMembers(members: Set): GroupId.Mms {
+ //language=sql
+ val statement = """
+ SELECT ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID} as gid
+ FROM ${MembershipTable.TABLE_NAME}
+ INNER JOIN $TABLE_NAME ON ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID} = $TABLE_NAME.$GROUP_ID
+ WHERE ${MembershipTable.TABLE_NAME}.$RECIPIENT_ID IN (${members.joinToString(",") { it.serialize() }}) AND $TABLE_NAME.$MMS = 1
+ GROUP BY ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID}
+ HAVING (SELECT COUNT(*) FROM ${MembershipTable.TABLE_NAME} WHERE ${MembershipTable.GROUP_ID} = gid) = ${members.size}
+ ORDER BY ${MembershipTable.TABLE_NAME}.${MembershipTable.ID} ASC
+ """.toSingleLine()
- readableDatabase
- .select(GROUP_ID)
- .from(TABLE_NAME)
- .where("$MEMBERS = ? AND $MMS = ?", sortedMembers.serialize(), 1)
- .run()
- .use { cursor ->
- return if (cursor.moveToNext()) {
- GroupId.parseOrThrow(cursor.requireNonNullString(GROUP_ID)).requireMms()
- } else {
- val groupId = GroupId.createMms(SecureRandom())
- create(groupId, null, sortedMembers)
- groupId
- }
+ return readableDatabase.query(statement).use { cursor ->
+ if (cursor.moveToNext()) {
+ return GroupId.parseOrThrow(cursor.requireNonNullString("gid")).requireMms()
+ } else {
+ val groupId = GroupId.createMms(SecureRandom())
+ create(groupId, null, members)
+ groupId
}
+ }
}
@WorkerThread
@@ -493,9 +507,14 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
@WorkerThread
fun getGroupsContainingMember(recipientId: RecipientId, pushOnly: Boolean, includeInactive: Boolean): List {
- val table = "$TABLE_NAME INNER JOIN ${ThreadTable.TABLE_NAME} ON $TABLE_NAME.$RECIPIENT_ID = ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID}"
- var query = "$MEMBERS LIKE ?"
- var args = buildArgs("%${recipientId.serialize()}%")
+ //language=sql
+ val table = """
+ $JOINED_GROUP_SELECT
+ INNER JOIN ${ThreadTable.TABLE_NAME} ON $TABLE_NAME.$RECIPIENT_ID = ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID}
+ """.toSingleLine()
+
+ var query = "${MembershipTable.TABLE_NAME}.${MembershipTable.RECIPIENT_ID} = ?"
+ var args = buildArgs(recipientId)
val orderBy = "${ThreadTable.TABLE_NAME}.${ThreadTable.DATE} DESC"
if (pushOnly) {
@@ -509,23 +528,14 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
}
return readableDatabase
- .query(table, null, query, args, null, null, orderBy)
+ .query("$table WHERE $query GROUP BY ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID} ORDER BY $orderBy".apply { println(this) }, args)
.readToList { cursor ->
- val serializedMembers = cursor.requireNonNullString(MEMBERS)
- if (RecipientId.serializedListContains(serializedMembers, recipientId)) {
- getGroup(cursor).get()
- } else {
- null
- }
+ getGroup(cursor).get()
}
- .filterNotNull()
}
fun getGroups(): Reader {
- val cursor = readableDatabase
- .select()
- .from(TABLE_NAME)
- .run()
+ val cursor = readableDatabase.query("$JOINED_GROUP_SELECT GROUP BY ${MembershipTable.TABLE_NAME}.${MembershipTable.GROUP_ID}")
return Reader(cursor)
}
@@ -648,6 +658,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
groupMasterKey: GroupMasterKey?,
groupState: DecryptedGroup?
) {
+ val membershipValues = mutableListOf()
val groupRecipientId = recipients.getOrInsertFromGroupId(groupId)
val members: List = memberCollection.toSet().sorted()
var groupMembers: List = members
@@ -657,7 +668,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
values.put(RECIPIENT_ID, groupRecipientId.serialize())
values.put(GROUP_ID, groupId.toString())
values.put(TITLE, title)
- values.put(MEMBERS, members.serialize())
+ membershipValues.addAll(members.toContentValues(groupId))
values.put(MMS, groupId.isMms)
if (avatar != null) {
@@ -693,14 +704,25 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
values.put(V2_MASTER_KEY, groupMasterKey.serialize())
values.put(V2_REVISION, groupState.revision)
values.put(V2_DECRYPTED_GROUP, groupState.toByteArray())
- values.put(MEMBERS, groupMembers.serialize())
+ membershipValues.clear()
+ membershipValues.addAll(groupMembers.toContentValues(groupId))
} else {
if (groupId.isV2) {
throw AssertionError("V2 group id but no master key")
}
}
- writableDatabase.insert(TABLE_NAME, null, values)
+ writableDatabase.withinTransaction { db ->
+ db.insert(TABLE_NAME, null, values)
+ SqlUtil.buildBulkInsert(
+ MembershipTable.TABLE_NAME,
+ arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID),
+ membershipValues
+ )
+ .forEach {
+ db.execSQL(it.where, it.whereArgs)
+ }
+ }
if (groupState != null && groupState.hasDisappearingMessagesTimer()) {
recipients.setExpireMessages(groupRecipientId, groupState.disappearingMessagesTimer.duration)
@@ -829,7 +851,6 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
}
val groupMembers = getV2GroupMembers(decryptedGroup, true)
- contentValues.put(MEMBERS, groupMembers.serialize())
if (existingGroup.isPresent && existingGroup.get().isV2Group) {
val change = GroupChangeReconstruct.reconstructGroupChange(existingGroup.get().requireV2GroupProperties().decryptedGroup, decryptedGroup)
@@ -842,11 +863,15 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
}
}
- writableDatabase
- .update(TABLE_NAME)
- .values(contentValues)
- .where("$GROUP_ID = ?", groupId.toString())
- .run()
+ writableDatabase.withinTransaction { database ->
+ database
+ .update(TABLE_NAME)
+ .values(contentValues)
+ .where("$GROUP_ID = ?", groupId.toString())
+ .run()
+
+ performMembershipUpdate(database, groupId, groupMembers)
+ }
if (decryptedGroup.hasDisappearingMessagesTimer()) {
recipients.setExpireMessages(groupRecipientId, decryptedGroup.disappearingMessagesTimer.duration)
@@ -898,27 +923,24 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
}
fun updateMembers(groupId: GroupId, members: List) {
- writableDatabase
- .update(TABLE_NAME)
- .values(
- MEMBERS to members.sorted().serialize(),
- ACTIVE to 1
- )
- .where("$GROUP_ID = ?", groupId)
- .run()
+ writableDatabase.withinTransaction { database ->
+ database
+ .update(TABLE_NAME)
+ .values(ACTIVE to 1)
+ .where("$GROUP_ID = ?", groupId)
+ .run()
+
+ performMembershipUpdate(database, groupId, members)
+ }
val groupRecipient = recipients.getOrInsertFromGroupId(groupId)
Recipient.live(groupRecipient).refresh()
}
fun remove(groupId: GroupId, source: RecipientId) {
- val currentMembers: MutableList = getCurrentMembers(groupId)
- currentMembers -= source
-
writableDatabase
- .update(TABLE_NAME)
- .values(MEMBERS to currentMembers.serialize())
- .where("$GROUP_ID = ?", groupId)
+ .delete(MembershipTable.TABLE_NAME)
+ .where("${MembershipTable.GROUP_ID} = ? AND ${MembershipTable.RECIPIENT_ID} = ?", groupId, source)
.run()
val groupRecipient = recipients.getOrInsertFromGroupId(groupId)
@@ -927,17 +949,34 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
private fun getCurrentMembers(groupId: GroupId): MutableList {
return readableDatabase
- .select(MEMBERS)
- .from(TABLE_NAME)
- .where("$GROUP_ID = ?", groupId)
+ .select(MembershipTable.RECIPIENT_ID)
+ .from(MembershipTable.TABLE_NAME)
+ .where("${MembershipTable.GROUP_ID} = ?", groupId)
.run()
.readToList { cursor ->
- val serializedMembers = cursor.requireNonNullString(MEMBERS)
- return RecipientId.fromSerializedList(serializedMembers)
+ RecipientId.from(cursor.requireLong(MembershipTable.RECIPIENT_ID))
}
.toMutableList()
}
+ private fun performMembershipUpdate(database: SQLiteDatabase, groupId: GroupId, members: Collection) {
+ check(database.inTransaction())
+ database
+ .delete(MembershipTable.TABLE_NAME)
+ .where("${MembershipTable.GROUP_ID} = ?", groupId)
+ .run()
+
+ val inserts = SqlUtil.buildBulkInsert(
+ MembershipTable.TABLE_NAME,
+ arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID),
+ members.toContentValues(groupId)
+ )
+
+ inserts.forEach {
+ database.execSQL(it.where, it.whereArgs)
+ }
+ }
+
fun isActive(groupId: GroupId): Boolean {
val record = getGroup(groupId)
return record.isPresent && record.get().isActive
@@ -961,19 +1000,10 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
@WorkerThread
fun isCurrentMember(groupId: Push, recipientId: RecipientId): Boolean {
- readableDatabase
- .select(MEMBERS)
- .from(TABLE_NAME)
- .where("$GROUP_ID = ?", groupId)
+ return readableDatabase
+ .exists(MembershipTable.TABLE_NAME)
+ .where("${MembershipTable.GROUP_ID} = ? AND ${MembershipTable.RECIPIENT_ID} = ?", groupId, recipientId)
.run()
- .use { cursor ->
- return if (cursor.moveToFirst()) {
- val serializedMembers = cursor.requireNonNullString(MEMBERS)
- RecipientId.serializedListContains(serializedMembers, recipientId)
- } else {
- false
- }
- }
}
fun getAllGroupV2Ids(): List {
@@ -1005,15 +1035,13 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
}
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
+ writableDatabase
+ .update(MembershipTable.TABLE_NAME)
+ .values(RECIPIENT_ID to toId.serialize())
+ .where("${MembershipTable.RECIPIENT_ID} = ?", fromId)
+ .run()
+
for (group in getGroupsContainingMember(fromId, false, true)) {
- val newMembers: Set = group.members.toSet() - fromId + toId
-
- writableDatabase
- .update(TABLE_NAME)
- .values(MEMBERS to newMembers.serialize())
- .where("$RECIPIENT_ID = ?", group.recipientId)
- .run()
-
if (group.isV2Group) {
removeUnmigratedV1Members(group.id.requireV2(), listOf(fromId))
}
@@ -1042,7 +1070,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
id = GroupId.parseOrThrow(cursor.requireNonNullString(GROUP_ID)),
recipientId = RecipientId.from(cursor.requireNonNullString(RECIPIENT_ID)),
title = cursor.requireString(TITLE),
- serializedMembers = cursor.requireString(MEMBERS),
+ serializedMembers = cursor.requireString(MEMBER_GROUP_CONCAT),
serializedUnmigratedV1Members = cursor.requireString(UNMIGRATED_V1_MEMBERS),
avatarId = cursor.requireLong(AVATAR_ID),
avatarKey = cursor.requireBlob(AVATAR_KEY),
@@ -1252,6 +1280,15 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
return RecipientId.toSerializedList(this)
}
+ private fun Collection.toContentValues(groupId: GroupId): List {
+ return map {
+ contentValuesOf(
+ MembershipTable.GROUP_ID to groupId.serialize(),
+ MembershipTable.RECIPIENT_ID to it.serialize()
+ )
+ }
+ }
+
private fun uuidsToRecipientIds(uuids: List): MutableList {
return uuids
.asSequence()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt
index d037f78c5..f21a8f386 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt
@@ -86,7 +86,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
db.execSQL(IdentityTable.CREATE_TABLE)
db.execSQL(DraftTable.CREATE_TABLE)
db.execSQL(PushTable.CREATE_TABLE)
- db.execSQL(GroupTable.CREATE_TABLE)
+ executeStatements(db, GroupTable.CREATE_TABLES)
db.execSQL(RecipientTable.CREATE_TABLE)
db.execSQL(GroupReceiptTable.CREATE_TABLE)
db.execSQL(OneTimePreKeyTable.CREATE_TABLE)
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 1a0fc83a7..a5983f114 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt
@@ -1610,12 +1610,17 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
private fun createQuery(where: String, orderBy: String, offset: Long, limit: Long): String {
val projection = COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION.joinToString(separator = ",")
+ //language=sql
var query = """
- SELECT $projection
+ SELECT $projection, ${GroupTable.MEMBER_GROUP_CONCAT}
FROM $TABLE_NAME
LEFT OUTER JOIN ${RecipientTable.TABLE_NAME} ON $TABLE_NAME.$RECIPIENT_ID = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID}
- LEFT OUTER JOIN ${GroupTable.TABLE_NAME} ON $TABLE_NAME.$RECIPIENT_ID = ${GroupTable.TABLE_NAME}.${GroupTable.RECIPIENT_ID}
- WHERE $where
+ LEFT OUTER JOIN ${GroupTable.TABLE_NAME} ON $TABLE_NAME.$RECIPIENT_ID = ${GroupTable.TABLE_NAME}.${GroupTable.RECIPIENT_ID}
+ LEFT OUTER JOIN (
+ SELECT group_id, GROUP_CONCAT(${GroupTable.MembershipTable.TABLE_NAME}.${GroupTable.MembershipTable.RECIPIENT_ID}) as ${GroupTable.MEMBER_GROUP_CONCAT}
+ FROM ${GroupTable.MembershipTable.TABLE_NAME}
+ ) as MembershipAlias ON MembershipAlias.${GroupTable.MembershipTable.GROUP_ID} = ${GroupTable.TABLE_NAME}.${GroupTable.GROUP_ID}
+ WHERE $where
ORDER BY $orderBy
""".trimIndent()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt
index cdd22f0c6..acde7cb6d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt
@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V168_SingleMessageT
import org.thoughtcrime.securesms.database.helpers.migration.V169_EmojiSearchIndexRank
import org.thoughtcrime.securesms.database.helpers.migration.V170_CallTableMigration
import org.thoughtcrime.securesms.database.helpers.migration.V171_ThreadForeignKeyFix
+import org.thoughtcrime.securesms.database.helpers.migration.V172_GroupMembershipMigration
/**
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
@@ -35,7 +36,7 @@ object SignalDatabaseMigrations {
val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass)
- const val DATABASE_VERSION = 171
+ const val DATABASE_VERSION = 172
@JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
@@ -130,6 +131,10 @@ object SignalDatabaseMigrations {
if (oldVersion < 171) {
V171_ThreadForeignKeyFix.migrate(context, db, oldVersion, newVersion)
}
+
+ if (oldVersion < 172) {
+ V172_GroupMembershipMigration.migrate(context, db, oldVersion, newVersion)
+ }
}
@JvmStatic
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V172_GroupMembershipMigration.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V172_GroupMembershipMigration.kt
new file mode 100644
index 000000000..659fb7b84
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V172_GroupMembershipMigration.kt
@@ -0,0 +1,65 @@
+package org.thoughtcrime.securesms.database.helpers.migration
+
+import android.app.Application
+import androidx.core.content.contentValuesOf
+import net.zetetic.database.sqlcipher.SQLiteDatabase
+import org.signal.core.util.SqlUtil
+import org.signal.core.util.readToList
+import org.signal.core.util.requireNonNullString
+
+/**
+ * Migrates all IDs from the GroupTable into the GroupMembershipTable
+ */
+@Suppress("ClassName")
+object V172_GroupMembershipMigration : SignalDatabaseMigration {
+ override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
+ db.execSQL(
+ """
+ CREATE TABLE group_membership (
+ _id INTEGER PRIMARY KEY,
+ group_id TEXT NOT NULL,
+ recipient_id INTEGER NOT NULL,
+ UNIQUE(group_id, recipient_id)
+ );
+ """.trimIndent()
+ )
+
+ //language=sql
+ val total = db.query("SELECT COUNT(*) FROM groups").use {
+ if (it.moveToFirst()) {
+ it.getInt(0)
+ } else {
+ 0
+ }
+ }
+
+ (0..total).chunked(500).forEachIndexed { index, _ ->
+ //language=sql
+ val groupIdToMembers: List>> = db.query("SELECT members, group_id FROM groups LIMIT 500 OFFSET ${index * 500}").readToList { cursor ->
+ val groupId = cursor.requireNonNullString("group_id")
+ val members: List = cursor.requireNonNullString("members").split(",").filterNot { it.isEmpty() }.map { it.toLong() }
+
+ groupId to members
+ }
+
+ for ((group_id, members) in groupIdToMembers) {
+ val queries = SqlUtil.buildBulkInsert(
+ "group_membership",
+ arrayOf("group_id", "recipient_id"),
+ members.map {
+ contentValuesOf(
+ "group_id" to group_id,
+ "recipient_id" to it
+ )
+ }
+ )
+
+ for (query in queries) {
+ db.execSQL(query.where, query.whereArgs)
+ }
+ }
+ }
+
+ db.execSQL("ALTER TABLE groups DROP COLUMN members")
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java
index bf3a92e9d..b5c4a8b3a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java
@@ -245,7 +245,7 @@ public class MmsDownloadJob extends BaseJob {
}
if (members.size() > 2) {
- List recipients = new ArrayList<>(members);
+ Set recipients = new HashSet<>(members);
group = Optional.of(SignalDatabase.groups().getOrCreateMmsGroupForMembers(recipients));
}
IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, TimeUnit.SECONDS.toMillis(retrieved.getDate()), -1, System.currentTimeMillis(), attachments, subscriptionId, 0, false, false, false, Optional.of(sharedContacts), false, false);
diff --git a/app/src/spinner/java/org/thoughtcrime/securesms/database/GV2Transformer.kt b/app/src/spinner/java/org/thoughtcrime/securesms/database/GV2Transformer.kt
index af1e35d19..2b9505405 100644
--- a/app/src/spinner/java/org/thoughtcrime/securesms/database/GV2Transformer.kt
+++ b/app/src/spinner/java/org/thoughtcrime/securesms/database/GV2Transformer.kt
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.database
import android.database.Cursor
import com.google.protobuf.ByteString
import org.signal.core.util.requireBlob
-import org.signal.core.util.requireString
import org.signal.spinner.ColumnTransformer
import org.signal.storageservice.protos.groups.local.DecryptedBannedMember
import org.signal.storageservice.protos.groups.local.DecryptedGroup
@@ -14,7 +13,7 @@ import org.whispersystems.signalservice.api.util.UuidUtil
object GV2Transformer : ColumnTransformer {
override fun matches(tableName: String?, columnName: String): Boolean {
- return columnName == GroupTable.V2_DECRYPTED_GROUP || columnName == GroupTable.MEMBERS
+ return columnName == GroupTable.V2_DECRYPTED_GROUP
}
override fun transform(tableName: String?, columnName: String, cursor: Cursor): String {
@@ -23,8 +22,7 @@ object GV2Transformer : ColumnTransformer {
val group = DecryptedGroup.parseFrom(groupBytes)
group.formatAsHtml()
} else {
- val members = cursor.requireString(GroupTable.MEMBERS)
- members?.split(',')?.chunked(20)?.joinToString("
") { it.joinToString(",") } ?: ""
+ ""
}
}
}