mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-03 15:11:42 +01:00
Add verified group title tracking and syncing.
This commit is contained in:
@@ -113,7 +113,7 @@ class ConversationHeaderView : AbstractComposeView {
|
||||
val isOfficialAccount = recipient.showVerified
|
||||
|
||||
val showUnverifiedName = if (recipient.isGroup) {
|
||||
!groupInfo.hasExistingContacts && !(groupInfo.fullMemberCount == 1 && groupInfo.isMember)
|
||||
!info.groupInfo.nameVerified
|
||||
} else if (!isOfficialAccount) {
|
||||
recipient.nickname.isEmpty && !recipient.isSystemContact
|
||||
} else {
|
||||
|
||||
@@ -74,6 +74,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPoin
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
|
||||
import org.whispersystems.signalservice.api.push.DistributionId
|
||||
import java.io.Closeable
|
||||
import java.security.MessageDigest
|
||||
import java.security.SecureRandom
|
||||
import java.time.Instant
|
||||
import java.util.Optional
|
||||
@@ -111,6 +112,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
|
||||
const val SHOW_AS_STORY_STATE = "show_as_story_state"
|
||||
const val LAST_FORCE_UPDATE_TIMESTAMP = "last_force_update_timestamp"
|
||||
const val GROUP_SEND_ENDORSEMENTS_EXPIRATION = "group_send_endorsements_expiration"
|
||||
const val V2_VERIFIED_NAME_HASH = "verified_name_hash"
|
||||
|
||||
/** 32 bytes serialized [GroupMasterKey] */
|
||||
const val V2_MASTER_KEY = "master_key"
|
||||
@@ -124,14 +126,14 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
|
||||
@JvmField
|
||||
val CREATE_TABLE = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
$ID INTEGER PRIMARY KEY,
|
||||
$GROUP_ID TEXT NOT NULL UNIQUE,
|
||||
$ID INTEGER PRIMARY KEY,
|
||||
$GROUP_ID TEXT NOT NULL UNIQUE,
|
||||
$RECIPIENT_ID INTEGER NOT NULL UNIQUE REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
|
||||
$TITLE TEXT DEFAULT NULL,
|
||||
$AVATAR_ID INTEGER DEFAULT 0,
|
||||
$AVATAR_ID INTEGER DEFAULT 0,
|
||||
$AVATAR_KEY BLOB DEFAULT NULL,
|
||||
$AVATAR_CONTENT_TYPE TEXT DEFAULT NULL,
|
||||
$AVATAR_DIGEST BLOB DEFAULT NULL,
|
||||
$AVATAR_CONTENT_TYPE TEXT DEFAULT NULL,
|
||||
$AVATAR_DIGEST BLOB DEFAULT NULL,
|
||||
$TIMESTAMP INTEGER DEFAULT 0,
|
||||
$IS_MEMBER INTEGER DEFAULT 1,
|
||||
$MMS INTEGER DEFAULT 0,
|
||||
@@ -144,7 +146,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
|
||||
$SHOW_AS_STORY_STATE INTEGER DEFAULT ${ShowAsStoryState.IF_ACTIVE.code},
|
||||
$LAST_FORCE_UPDATE_TIMESTAMP INTEGER DEFAULT 0,
|
||||
$GROUP_SEND_ENDORSEMENTS_EXPIRATION INTEGER DEFAULT 0,
|
||||
$TERMINATED_BY INTEGER DEFAULT 0
|
||||
$TERMINATED_BY INTEGER DEFAULT 0,
|
||||
$V2_VERIFIED_NAME_HASH BLOB DEFAULT NULL
|
||||
)
|
||||
"""
|
||||
|
||||
@@ -167,6 +170,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
|
||||
V2_MASTER_KEY,
|
||||
V2_REVISION,
|
||||
V2_DECRYPTED_GROUP,
|
||||
V2_VERIFIED_NAME_HASH,
|
||||
LAST_FORCE_UPDATE_TIMESTAMP,
|
||||
GROUP_SEND_ENDORSEMENTS_EXPIRATION
|
||||
)
|
||||
@@ -177,6 +181,11 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
|
||||
.toList()
|
||||
|
||||
val CREATE_TABLES = arrayOf(CREATE_TABLE, MembershipTable.CREATE_TABLE)
|
||||
|
||||
@JvmStatic
|
||||
fun computeVerifiedNameHash(title: String?): ByteArray? {
|
||||
return title?.let { MessageDigest.getInstance("SHA-256").digest(it.toByteArray(Charsets.UTF_8)) }
|
||||
}
|
||||
}
|
||||
|
||||
class MembershipTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper) {
|
||||
@@ -544,6 +553,14 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
|
||||
.run()
|
||||
}
|
||||
|
||||
fun setVerifiedGroupNameHash(groupId: GroupId.V2, verifiedNameHash: ByteArray?) {
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(V2_VERIFIED_NAME_HASH to verifiedNameHash)
|
||||
.where("$GROUP_ID = ?", groupId)
|
||||
.run()
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun getGroupMemberIds(groupId: GroupId, memberSet: MemberSet): List<RecipientId> {
|
||||
return if (groupId.isV2) {
|
||||
@@ -607,19 +624,25 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
|
||||
throw LegacyGroupInsertException(groupId)
|
||||
}
|
||||
|
||||
return create(groupId, title, members, avatar, null, null, null)
|
||||
return create(groupId, title, members, avatar, null, null, null, null)
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
fun create(groupId: GroupId.Mms, title: String?, members: Collection<RecipientId>): Boolean {
|
||||
return create(groupId, if (title.isNullOrEmpty()) null else title, members, null, null, null, null)
|
||||
return create(groupId, if (title.isNullOrEmpty()) null else title, members, null, null, null, null, null)
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
fun create(groupMasterKey: GroupMasterKey, groupState: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?): GroupId.V2? {
|
||||
@JvmOverloads
|
||||
fun create(
|
||||
groupMasterKey: GroupMasterKey,
|
||||
groupState: DecryptedGroup,
|
||||
groupSendEndorsements: ReceivedGroupSendEndorsements?,
|
||||
verifiedNameHash: ByteArray? = null
|
||||
): GroupId.V2? {
|
||||
val groupId = GroupId.v2(groupMasterKey)
|
||||
|
||||
return if (create(groupId = groupId, title = groupState.title, memberCollection = emptyList(), avatar = null, groupMasterKey = groupMasterKey, groupState = groupState, receivedGroupSendEndorsements = groupSendEndorsements)) {
|
||||
return if (create(groupId = groupId, title = groupState.title, memberCollection = emptyList(), avatar = null, groupMasterKey = groupMasterKey, groupState = groupState, receivedGroupSendEndorsements = groupSendEndorsements, verifiedNameHash = verifiedNameHash)) {
|
||||
groupId
|
||||
} else {
|
||||
null
|
||||
@@ -667,7 +690,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
|
||||
avatar: SignalServiceAttachmentPointer?,
|
||||
groupMasterKey: GroupMasterKey?,
|
||||
groupState: DecryptedGroup?,
|
||||
receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?
|
||||
receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?,
|
||||
verifiedNameHash: ByteArray?
|
||||
): Boolean {
|
||||
val membershipValues = mutableListOf<ContentValues>()
|
||||
val groupRecipientId = recipients.getOrInsertFromGroupId(groupId)
|
||||
@@ -716,6 +740,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
|
||||
values.put(V2_MASTER_KEY, groupMasterKey.serialize())
|
||||
values.put(V2_REVISION, groupState.revision)
|
||||
values.put(V2_DECRYPTED_GROUP, groupState.encode())
|
||||
values.put(V2_VERIFIED_NAME_HASH, verifiedNameHash)
|
||||
membershipValues.clear()
|
||||
membershipValues.addAll(groupMembers.toContentValues(groupId, receivedGroupSendEndorsements?.toGroupSendEndorsementRecords()))
|
||||
} else {
|
||||
@@ -787,11 +812,25 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
|
||||
notifyConversationListListeners()
|
||||
}
|
||||
|
||||
fun update(groupMasterKey: GroupMasterKey, decryptedGroup: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?, terminatorRecipientId: RecipientId? = null) {
|
||||
update(GroupId.v2(groupMasterKey), decryptedGroup, groupSendEndorsements, terminatorRecipientId)
|
||||
@JvmOverloads
|
||||
fun update(
|
||||
groupMasterKey: GroupMasterKey,
|
||||
decryptedGroup: DecryptedGroup,
|
||||
groupSendEndorsements: ReceivedGroupSendEndorsements?,
|
||||
terminatorRecipientId: RecipientId? = null,
|
||||
selfAuthoredTitle: Boolean = false
|
||||
) {
|
||||
update(GroupId.v2(groupMasterKey), decryptedGroup, groupSendEndorsements, terminatorRecipientId, selfAuthoredTitle)
|
||||
}
|
||||
|
||||
fun update(groupId: GroupId.V2, decryptedGroup: DecryptedGroup, receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?, terminatorRecipientId: RecipientId? = null) {
|
||||
@JvmOverloads
|
||||
fun update(
|
||||
groupId: GroupId.V2,
|
||||
decryptedGroup: DecryptedGroup,
|
||||
receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?,
|
||||
terminatorRecipientId: RecipientId? = null,
|
||||
selfAuthoredTitle: Boolean = false
|
||||
) {
|
||||
val groupRecipientId: RecipientId = recipients.getOrInsertFromGroupId(groupId)
|
||||
val existingGroup: Optional<GroupRecord> = getGroup(groupId)
|
||||
val title: String = decryptedGroup.title
|
||||
@@ -803,6 +842,10 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
|
||||
contentValues.put(IS_MEMBER, if (isGroupMember(decryptedGroup)) 1 else 0)
|
||||
contentValues.put(TERMINATED_BY, terminatorRecipientId?.toLong() ?: if (decryptedGroup.terminated) -1 else 0)
|
||||
|
||||
if (selfAuthoredTitle) {
|
||||
contentValues.put(V2_VERIFIED_NAME_HASH, computeVerifiedNameHash(title))
|
||||
}
|
||||
|
||||
if (receivedGroupSendEndorsements != null) {
|
||||
contentValues.put(GROUP_SEND_ENDORSEMENTS_EXPIRATION, receivedGroupSendEndorsements.expirationMs)
|
||||
}
|
||||
@@ -1165,6 +1208,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
|
||||
groupMasterKeyBytes = cursor.requireBlob(V2_MASTER_KEY),
|
||||
groupRevision = cursor.requireInt(V2_REVISION),
|
||||
decryptedGroupBytes = cursor.requireBlob(V2_DECRYPTED_GROUP),
|
||||
verifiedNameHash = cursor.requireBlob(V2_VERIFIED_NAME_HASH),
|
||||
distributionId = cursor.optionalString(DISTRIBUTION_ID).map { id -> DistributionId.from(id) }.orElse(null),
|
||||
lastForceUpdateTimestamp = cursor.requireLong(LAST_FORCE_UPDATE_TIMESTAMP),
|
||||
groupSendEndorsementExpiration = cursor.requireLong(GROUP_SEND_ENDORSEMENTS_EXPIRATION)
|
||||
|
||||
@@ -1013,6 +1013,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
val masterKey = GroupMasterKey(insert.proto.masterKey.toByteArray())
|
||||
val groupId = GroupId.v2(masterKey)
|
||||
val values = getValuesForStorageGroupV2(insert, true)
|
||||
val verifiedNameHash: ByteArray? = insert.proto.verifiedNameHash.nullIfEmpty()?.toByteArray()
|
||||
|
||||
val createdId = writableDatabase.withinTransaction {
|
||||
writableDatabase.insertOrThrow(TABLE_NAME, null, values)
|
||||
@@ -1021,12 +1022,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
groups.create(
|
||||
groupMasterKey = masterKey,
|
||||
groupState = DecryptedGroup(revision = GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION),
|
||||
groupSendEndorsements = null
|
||||
groupSendEndorsements = null,
|
||||
verifiedNameHash = verifiedNameHash
|
||||
)
|
||||
}
|
||||
|
||||
if (createdId == null) {
|
||||
Log.w(TAG, "Unable to create restore placeholder for $groupId, group already exists")
|
||||
groups.setVerifiedGroupNameHash(groupId, verifiedNameHash)
|
||||
}
|
||||
|
||||
groups.setShowAsStoryState(groupId, insert.proto.storySendMode.toShowAsStoryState())
|
||||
@@ -1040,7 +1043,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
Log.i(TAG, "Scheduling request for latest group info for $groupId")
|
||||
AppDependencies.jobManager.add(RequestGroupV2InfoJob(groupId))
|
||||
threads.applyStorageSyncUpdate(recipient.id, insert)
|
||||
recipient.live().refresh()
|
||||
AppDependencies.databaseObserver.notifyRecipientChanged(recipient.id)
|
||||
}
|
||||
|
||||
fun applyStorageSyncGroupV2Update(update: StorageRecordUpdate<SignalGroupV2Record>) {
|
||||
@@ -1060,8 +1063,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
|
||||
groups.setShowAsStoryState(groupId, update.new.proto.storySendMode.toShowAsStoryState())
|
||||
groups.setVerifiedGroupNameHash(groupId, update.new.proto.verifiedNameHash.nullIfEmpty()?.toByteArray())
|
||||
threads.applyStorageSyncUpdate(recipient.id, update.new)
|
||||
recipient.live().refresh()
|
||||
AppDependencies.databaseObserver.notifyRecipientChanged(recipient.id)
|
||||
}
|
||||
|
||||
fun applyStorageSyncAccountUpdate(update: StorageRecordUpdate<SignalAccountRecord>) {
|
||||
|
||||
@@ -168,6 +168,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V312_RefactorNameCo
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V313_AddCollapsingUpdateColumns
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V314_FixMessageRequestAcceptedToRecipient
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V315_CleanupE164SenderKeyShared
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V316_AddVerifiedGroupNameHashMigration
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSqliteDatabase
|
||||
|
||||
/**
|
||||
@@ -343,10 +344,11 @@ object SignalDatabaseMigrations {
|
||||
312 to V312_RefactorNameCollisionTables,
|
||||
313 to V313_AddCollapsingUpdateColumns,
|
||||
314 to V314_FixMessageRequestAcceptedToRecipient,
|
||||
315 to V315_CleanupE164SenderKeyShared
|
||||
315 to V315_CleanupE164SenderKeyShared,
|
||||
316 to V316_AddVerifiedGroupNameHashMigration
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 315
|
||||
const val DATABASE_VERSION = 316
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SignalSqliteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase
|
||||
|
||||
@Suppress("ClassName")
|
||||
object V316_AddVerifiedGroupNameHashMigration : SignalDatabaseMigration {
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("ALTER TABLE groups ADD COLUMN verified_name_hash BLOB DEFAULT NULL")
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ class GroupRecord(
|
||||
groupMasterKeyBytes: ByteArray?,
|
||||
groupRevision: Int,
|
||||
decryptedGroupBytes: ByteArray?,
|
||||
val verifiedNameHash: ByteArray? = null,
|
||||
val distributionId: DistributionId?,
|
||||
val lastForceUpdateTimestamp: Long,
|
||||
val groupSendEndorsementExpiration: Long
|
||||
|
||||
@@ -53,6 +53,7 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptChangeVerificationMode;
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupExtensions;
|
||||
@@ -235,7 +236,7 @@ final class GroupManagerV2 {
|
||||
DecryptedGroup decryptedGroup = createGroupResponse.getGroup();
|
||||
GroupMasterKey masterKey = groupSecretParams.getMasterKey();
|
||||
ReceivedGroupSendEndorsements groupSendEndorsements = groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroup, createGroupResponse.getGroupSendEndorsementsResponse());
|
||||
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup, groupSendEndorsements);
|
||||
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup, groupSendEndorsements, GroupTable.computeVerifiedNameHash(decryptedGroup.title));
|
||||
|
||||
if (groupId == null) {
|
||||
throw new GroupChangeFailedException("Unable to create group, group already exists");
|
||||
@@ -745,7 +746,14 @@ final class GroupManagerV2 {
|
||||
}
|
||||
|
||||
RecipientId terminatorRecipientId = (decryptedGroupState.terminated && !previousGroupState.terminated) ? Recipient.self().getId() : null;
|
||||
groupDatabase.update(groupId, decryptedGroupState, groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroupState, changeResponse.group_send_endorsements_response), terminatorRecipientId);
|
||||
boolean selfAuthoredTitle = changeActions.modifyTitle != null;
|
||||
groupDatabase.update(groupId, decryptedGroupState, groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroupState, changeResponse.group_send_endorsements_response), terminatorRecipientId, selfAuthoredTitle);
|
||||
|
||||
if (selfAuthoredTitle) {
|
||||
RecipientId groupRecipientId = SignalDatabase.recipients().getOrInsertFromGroupId(groupId);
|
||||
SignalDatabase.recipients().rotateStorageId(groupRecipientId, false);
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
}
|
||||
|
||||
GroupMutation groupMutation = new GroupMutation(previousGroupState, decryptedChange, decryptedGroupState);
|
||||
RecipientAndThread recipientAndThread = sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, groupMutation, signedGroupChange, sendToMembers);
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.signal.libsignal.zkgroup.groups.GroupSecretParams
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup
|
||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange
|
||||
import org.thoughtcrime.securesms.database.GroupTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord
|
||||
import org.thoughtcrime.securesms.database.model.GroupsV2UpdateMessageConverter
|
||||
@@ -42,6 +43,7 @@ import org.thoughtcrime.securesms.mms.MmsException
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil
|
||||
@@ -635,6 +637,7 @@ class GroupsV2StateProcessor private constructor(
|
||||
val terminatorRecipientId: RecipientId? = if (wasTerminated) {
|
||||
groupStateDiff
|
||||
.serverHistory
|
||||
.asSequence()
|
||||
.mapNotNull { it.change }
|
||||
.firstOrNull { it.terminateGroup }
|
||||
?.let { ServiceId.parseOrNull(it.editorServiceIdBytes) }
|
||||
@@ -643,17 +646,42 @@ class GroupsV2StateProcessor private constructor(
|
||||
null
|
||||
}
|
||||
|
||||
val selfAuthoredTitle: Boolean = run {
|
||||
val lastTitleChange = groupStateDiff
|
||||
.serverHistory
|
||||
.asSequence()
|
||||
.mapNotNull { it.change }
|
||||
.lastOrNull { it.newTitle != null }
|
||||
|
||||
if (lastTitleChange != null) {
|
||||
return@run ServiceId.parseOrNull(lastTitleChange.editorServiceIdBytes) == serviceIds.aci
|
||||
}
|
||||
|
||||
if (previousGroupState == null && updatedGroupState.revision == 0) {
|
||||
val rev0Editor = groupStateDiff
|
||||
.serverHistory
|
||||
.firstOrNull { it.group?.revision == 0 }
|
||||
?.change
|
||||
?.let { ServiceId.parseOrNull(it.editorServiceIdBytes) }
|
||||
|
||||
return@run rev0Editor == serviceIds.aci
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
val needsAvatarFetch = if (previousGroupState == null) {
|
||||
val groupId = SignalDatabase.groups.create(groupMasterKey, updatedGroupState, groupSendEndorsements)
|
||||
val verifiedNameHash: ByteArray? = if (selfAuthoredTitle) GroupTable.computeVerifiedNameHash(updatedGroupState.title) else null
|
||||
val groupId = SignalDatabase.groups.create(groupMasterKey, updatedGroupState, groupSendEndorsements, verifiedNameHash)
|
||||
|
||||
if (groupId == null) {
|
||||
Log.w(TAG, "$logPrefix Group create failed, trying to update")
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements, terminatorRecipientId)
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements, terminatorRecipientId, selfAuthoredTitle)
|
||||
}
|
||||
|
||||
updatedGroupState.avatar.isNotEmpty()
|
||||
} else {
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements, terminatorRecipientId)
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements, terminatorRecipientId, selfAuthoredTitle)
|
||||
|
||||
updatedGroupState.avatar != previousGroupState.avatar
|
||||
}
|
||||
@@ -667,6 +695,10 @@ class GroupsV2StateProcessor private constructor(
|
||||
AppDependencies.jobManager.add(AvatarGroupsV2DownloadJob(groupId, updatedGroupState.avatar))
|
||||
}
|
||||
|
||||
if (selfAuthoredTitle) {
|
||||
profileAndMessageHelper.scheduleStorageServiceSync()
|
||||
}
|
||||
|
||||
profileAndMessageHelper.setProfileSharing(groupStateDiff, updatedGroupState, needsAvatarFetch)
|
||||
}
|
||||
|
||||
@@ -1005,6 +1037,13 @@ class GroupsV2StateProcessor private constructor(
|
||||
return Optional.empty()
|
||||
}
|
||||
|
||||
fun scheduleStorageServiceSync() {
|
||||
val groupRecipientId = SignalDatabase.recipients.getOrInsertFromGroupId(groupId)
|
||||
SignalDatabase.recipients.rotateStorageId(groupRecipientId)
|
||||
Recipient.live(groupRecipientId).refresh()
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@VisibleForTesting
|
||||
fun create(aci: ACI, masterKey: GroupMasterKey, groupId: GroupId.V2): ProfileAndMessageHelper {
|
||||
|
||||
@@ -12,7 +12,8 @@ class GroupInfo(
|
||||
val hasExistingContacts: Boolean = false,
|
||||
val membersPreview: List<Recipient> = emptyList(),
|
||||
val isMember: Boolean = false,
|
||||
val isTerminated: Boolean = false
|
||||
val isTerminated: Boolean = false,
|
||||
val nameVerified: Boolean = false
|
||||
) {
|
||||
companion object {
|
||||
@JvmField
|
||||
|
||||
@@ -31,11 +31,10 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.internal.push.exceptions.GroupPatchNotAcceptedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -73,12 +72,13 @@ public final class MessageRequestRepository {
|
||||
boolean groupHasExistingContacts = recipients.stream().filter(r -> !r.isSelf()).anyMatch(r -> r.isProfileSharing() || r.isSystemContact());
|
||||
List<Recipient> membersPreview = recipients.stream().filter(r -> !r.isSelf()).limit(MAX_MEMBER_NAMES).collect(Collectors.toList());
|
||||
DecryptedGroup decryptedGroup = groupRecord.get().requireV2GroupProperties().getDecryptedGroup();
|
||||
boolean nameVerified = groupRecord.get().getVerifiedNameHash() != null && Arrays.equals(GroupTable.computeVerifiedNameHash(groupRecord.get().getTitle()), groupRecord.get().getVerifiedNameHash());
|
||||
|
||||
groupInfo = new GroupInfo(decryptedGroup.members.size(), decryptedGroup.pendingMembers.size(), decryptedGroup.description, groupHasExistingContacts, membersPreview, groupRecord.get().isMember(), groupRecord.get().isTerminated());
|
||||
groupInfo = new GroupInfo(decryptedGroup.members.size(), decryptedGroup.pendingMembers.size(), decryptedGroup.description, groupHasExistingContacts, membersPreview, groupRecord.get().isMember(), groupRecord.get().isTerminated(), nameVerified);
|
||||
} else {
|
||||
List<Recipient> membersPreview = recipients.stream().filter(r -> !r.isSelf()).limit(MAX_MEMBER_NAMES).collect(Collectors.toList());
|
||||
|
||||
groupInfo = new GroupInfo(groupRecord.get().getMembers().size(), 0, "", false, membersPreview, groupRecord.get().isActive(), false);
|
||||
groupInfo = new GroupInfo(groupRecord.get().getMembers().size(), 0, "", false, membersPreview, groupRecord.get().isActive(), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms.storage
|
||||
|
||||
import org.signal.core.util.isNotEmpty
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.thoughtcrime.securesms.database.GroupTable
|
||||
@@ -60,6 +61,7 @@ class GroupV2RecordProcessor(private val recipientTable: RecipientTable, private
|
||||
hideStory = remote.proto.hideStory
|
||||
storySendMode = remote.proto.storySendMode
|
||||
avatarColor = if (SignalStore.account.isPrimaryDevice) local.proto.avatarColor else remote.proto.avatarColor
|
||||
verifiedNameHash = if (remote.proto.verifiedNameHash.isNotEmpty()) remote.proto.verifiedNameHash else local.proto.verifiedNameHash
|
||||
}.build().toSignalGroupV2Record(StorageId.forGroupV2(keyGenerator.generate()))
|
||||
|
||||
val matchesRemote = doParamsMatch(remote, merged)
|
||||
|
||||
@@ -242,6 +242,8 @@ object StorageSyncModels {
|
||||
throw AssertionError("Group is not V2")
|
||||
}
|
||||
|
||||
val localVerifiedNameHash: ByteArray? = groups.getGroup(groupId).orElse(null)?.verifiedNameHash
|
||||
|
||||
return SignalGroupV2Record.newBuilder(recipient.syncExtras.storageProto).apply {
|
||||
masterKey = groupMasterKey.serialize().toByteString()
|
||||
blocked = recipient.isBlocked
|
||||
@@ -257,6 +259,9 @@ object StorageSyncModels {
|
||||
ShowAsStoryState.NEVER -> GroupV2Record.StorySendMode.DISABLED
|
||||
else -> GroupV2Record.StorySendMode.DEFAULT
|
||||
}
|
||||
if (localVerifiedNameHash != null) {
|
||||
verifiedNameHash = localVerifiedNameHash.toByteString()
|
||||
}
|
||||
}.build().toSignalGroupV2Record(StorageId.forGroupV2(rawStorageId))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user