Convert and store new group changes in MessageExtras.
This commit is contained in:
parent
cc25f0685c
commit
1ade8b502f
17 changed files with 933 additions and 56 deletions
|
@ -9,6 +9,7 @@ import android.database.Cursor
|
|||
import com.annimon.stream.Stream
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Base64.decode
|
||||
import org.signal.core.util.Base64.decodeOrThrow
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.requireBlob
|
||||
|
@ -40,11 +41,15 @@ import org.thoughtcrime.securesms.database.SignalDatabase.Companion.calls
|
|||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchSet
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailureSet
|
||||
import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil
|
||||
import org.thoughtcrime.securesms.database.model.GroupsV2UpdateMessageConverter
|
||||
import org.thoughtcrime.securesms.database.model.ReactionRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
|
@ -154,6 +159,26 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
|||
}
|
||||
)
|
||||
}
|
||||
MessageTypes.isGroupV2(record.type) && MessageTypes.isGroupUpdate(record.type) -> {
|
||||
val groupChange = record.messageExtras?.gv2UpdateDescription?.groupChangeUpdate
|
||||
if (groupChange != null) {
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
groupChange = groupChange
|
||||
)
|
||||
} else if (record.body != null) {
|
||||
try {
|
||||
val decoded: ByteArray = decode(record.body)
|
||||
val context = DecryptedGroupV2Context.ADAPTER.decode(decoded)
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
groupChange = GroupsV2UpdateMessageConverter.translateDecryptedChange(selfIds = SignalStore.account().getServiceIds(), context)
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
MessageTypes.isCallLog(record.type) -> {
|
||||
val call = calls.getCallByMessageId(record.id)
|
||||
if (call != null) {
|
||||
|
@ -412,6 +437,17 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
|||
}
|
||||
}
|
||||
|
||||
private fun ByteArray?.parseMessageExtras(): MessageExtras? {
|
||||
if (this == null) {
|
||||
return null
|
||||
}
|
||||
return try {
|
||||
MessageExtras.ADAPTER.decode(this)
|
||||
} catch (e: java.lang.Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Cursor.toBackupMessageRecord(): BackupMessageRecord {
|
||||
return BackupMessageRecord(
|
||||
id = this.requireLong(MessageTable.ID),
|
||||
|
@ -443,7 +479,8 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
|||
receiptTimestamp = this.requireLong(MessageTable.RECEIPT_TIMESTAMP),
|
||||
networkFailureRecipientIds = this.requireString(MessageTable.NETWORK_FAILURES).parseNetworkFailures(),
|
||||
identityMismatchRecipientIds = this.requireString(MessageTable.MISMATCHED_IDENTITIES).parseIdentityMismatches(),
|
||||
baseType = this.requireLong(COLUMN_BASE_TYPE)
|
||||
baseType = this.requireLong(COLUMN_BASE_TYPE),
|
||||
messageExtras = this.requireBlob(MessageTable.MESSAGE_EXTRAS).parseMessageExtras()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -477,6 +514,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
|||
val read: Boolean,
|
||||
val networkFailureRecipientIds: Set<Long>,
|
||||
val identityMismatchRecipientIds: Set<Long>,
|
||||
val baseType: Long
|
||||
val baseType: Long,
|
||||
val messageExtras: MessageExtras?
|
||||
)
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchSet
|
|||
import org.thoughtcrime.securesms.database.documents.NetworkFailure
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailureSet
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
|
||||
|
@ -410,6 +412,17 @@ class ChatItemImportInserter(
|
|||
// Calls don't use the incoming/outgoing flags, so we overwrite the flags here
|
||||
this.put(MessageTable.TYPE, typeFlags)
|
||||
}
|
||||
updateMessage.groupChange != null -> {
|
||||
put(MessageTable.BODY, "")
|
||||
put(
|
||||
MessageTable.MESSAGE_EXTRAS,
|
||||
MessageExtras(
|
||||
gv2UpdateDescription =
|
||||
GV2UpdateDescription(groupChangeUpdate = updateMessage.groupChange)
|
||||
).encode()
|
||||
)
|
||||
typeFlags = MessageTypes.GROUP_V2_BIT or MessageTypes.GROUP_UPDATE_BIT
|
||||
}
|
||||
}
|
||||
this.put(MessageTable.TYPE, getAsLong(MessageTable.TYPE) or typeFlags)
|
||||
}
|
||||
|
|
|
@ -47,7 +47,8 @@ fun MessageTable.getMessagesForBackup(): ChatItemExportIterator {
|
|||
MessageTable.READ,
|
||||
MessageTable.NETWORK_FAILURES,
|
||||
MessageTable.MISMATCHED_IDENTITIES,
|
||||
"${MessageTable.TYPE} & ${MessageTypes.BASE_TYPE_MASK} AS ${ChatItemExportIterator.COLUMN_BASE_TYPE}"
|
||||
"${MessageTable.TYPE} & ${MessageTypes.BASE_TYPE_MASK} AS ${ChatItemExportIterator.COLUMN_BASE_TYPE}",
|
||||
MessageTable.MESSAGE_EXTRAS
|
||||
)
|
||||
.from(MessageTable.TABLE_NAME)
|
||||
.where(
|
||||
|
|
|
@ -2305,6 +2305,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
val parentStoryId = ParentStoryId.deserialize(cursor.requireLong(PARENT_STORY_ID))
|
||||
val messageRangesData = cursor.requireBlob(MESSAGE_RANGES)
|
||||
val scheduledDate = cursor.requireLong(SCHEDULED_DATE)
|
||||
val messageExtrasBytes = cursor.requireBlob(MESSAGE_EXTRAS)
|
||||
val messageExtras = if (messageExtrasBytes != null) MessageExtras.ADAPTER.decode(messageExtrasBytes) else null
|
||||
|
||||
val quoteId = cursor.requireLong(QUOTE_ID)
|
||||
val quoteAuthor = cursor.requireLong(QUOTE_AUTHOR)
|
||||
|
@ -2357,7 +2359,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
if (body != null && (MessageTypes.isGroupQuit(outboxType) || MessageTypes.isGroupUpdate(outboxType))) {
|
||||
OutgoingMessage.groupUpdateMessage(
|
||||
threadRecipient = threadRecipient,
|
||||
groupContext = MessageGroupContext(body, MessageTypes.isGroupV2(outboxType)),
|
||||
groupContext = if (messageExtras != null) MessageGroupContext(messageExtras, MessageTypes.isGroupV2(outboxType)) else MessageGroupContext(body, MessageTypes.isGroupV2(outboxType)),
|
||||
avatar = attachments,
|
||||
sentTimeMillis = timestamp,
|
||||
expiresIn = 0,
|
||||
|
@ -2859,6 +2861,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
contentValues.put(PARENT_STORY_ID, if (message.parentStoryId != null) message.parentStoryId.serialize() else 0)
|
||||
contentValues.put(SCHEDULED_DATE, message.scheduledDate)
|
||||
contentValues.putNull(LATEST_REVISION_ID)
|
||||
contentValues.put(MESSAGE_EXTRAS, message.messageExtras?.encode())
|
||||
|
||||
if (editedMessage != null) {
|
||||
contentValues.put(ORIGINAL_MESSAGE_ID, editedMessage.getOriginalOrOwnMessageId().id)
|
||||
|
@ -5062,7 +5065,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
|||
val editCount = cursor.requireInt(REVISION_NUMBER)
|
||||
val isRead = cursor.requireBoolean(READ)
|
||||
val messageExtraBytes = cursor.requireBlob(MESSAGE_EXTRAS)
|
||||
val messageExtras = if (messageExtraBytes != null) MessageExtras.ADAPTER.decode(messageExtraBytes) else null
|
||||
val messageExtras = messageExtraBytes?.let { MessageExtras.ADAPTER.decode(it) }
|
||||
|
||||
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||
hasReadReceipt = false
|
||||
|
|
|
@ -1929,7 +1929,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
|||
|
||||
val hasReadReceipt = TextSecurePreferences.isReadReceiptsEnabled(context) && cursor.requireBoolean(HAS_READ_RECEIPT)
|
||||
val extraString = cursor.getString(cursor.getColumnIndexOrThrow(SNIPPET_EXTRAS))
|
||||
val messageExtras = cursor.getBlob(cursor.getColumnIndexOrThrow(SNIPPET_MESSAGE_EXTRAS))
|
||||
val messageExtraBytes = cursor.getBlob(cursor.getColumnIndexOrThrow(SNIPPET_MESSAGE_EXTRAS))
|
||||
val messageExtras = if (messageExtraBytes != null) MessageExtras.ADAPTER.decode(messageExtraBytes) else null
|
||||
val extra: Extra? = if (extraString != null) {
|
||||
try {
|
||||
val jsonObject = SaneJSONObject(JSONObject(extraString))
|
||||
|
@ -1974,6 +1975,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
|||
.setPinned(cursor.requireBoolean(PINNED))
|
||||
.setUnreadSelfMentionsCount(cursor.requireInt(UNREAD_SELF_MENTION_COUNT))
|
||||
.setExtra(extra)
|
||||
.setSnippetMessageExtras(messageExtras)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,681 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.model
|
||||
|
||||
import ProtoUtil.isNullOrEmpty
|
||||
import okio.ByteString
|
||||
import org.signal.core.util.StringUtil
|
||||
import org.signal.storageservice.protos.groups.AccessControl
|
||||
import org.signal.storageservice.protos.groups.AccessControl.AccessRequired
|
||||
import org.signal.storageservice.protos.groups.Member
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember
|
||||
import org.signal.storageservice.protos.groups.local.EnabledState
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GenericGroupUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupAdminStatusUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupAnnouncementOnlyChangeUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupAttributesAccessLevelChangeUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupAvatarUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupChangeChatUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupCreationUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupDescriptionUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupExpirationTimerUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationAcceptedUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationDeclinedUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationRevokedUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupInviteLinkAdminApprovalUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupInviteLinkDisabledUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupInviteLinkEnabledUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupInviteLinkResetUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestApprovalUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestCanceledUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberAddedUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberJoinedByLinkUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberJoinedUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberLeftUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberRemovedUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMembershipAccessLevelChangeUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupNameUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupSelfInvitationRevokedUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupSequenceOfRequestsAndCancelsUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupUnknownInviteeUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2AccessLevel
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedOtherUserToGroupUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedToGroupUpdate
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.Companion.parseOrNull
|
||||
import org.whispersystems.signalservice.api.push.ServiceIds
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
import java.util.LinkedList
|
||||
import java.util.Optional
|
||||
import java.util.stream.Collectors
|
||||
|
||||
/**
|
||||
* Object to help with the translation between DecryptedGroupV2Context group updates
|
||||
* and GroupChangeChatUpdates, which store the update messages as distinct messages rather
|
||||
* than diffs of the group state.
|
||||
*/
|
||||
object GroupsV2UpdateMessageConverter {
|
||||
|
||||
@JvmStatic
|
||||
fun translateDecryptedChange(selfIds: ServiceIds, groupContext: DecryptedGroupV2Context): GroupChangeChatUpdate {
|
||||
if (groupContext.change != null && ((groupContext.groupState != null && groupContext.groupState.revision != 0) || groupContext.previousGroupState != null)) {
|
||||
return translateDecryptedChangeUpdate(selfIds, groupContext)
|
||||
} else {
|
||||
return translateDecryptedChangeNewGroup(selfIds, groupContext)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateDecryptedChangeNewGroup(selfIds: ServiceIds, groupContext: DecryptedGroupV2Context): GroupChangeChatUpdate {
|
||||
var selfPending = Optional.empty<DecryptedPendingMember>()
|
||||
val decryptedGroupChange = groupContext.change
|
||||
val group = groupContext.groupState
|
||||
val updates: MutableList<GroupChangeChatUpdate.Update> = LinkedList()
|
||||
|
||||
if (group != null) {
|
||||
selfPending = DecryptedGroupUtil.findPendingByServiceId(group.pendingMembers, selfIds.aci)
|
||||
if (selfPending.isEmpty() && selfIds.pni != null) {
|
||||
selfPending = DecryptedGroupUtil.findPendingByServiceId(group.pendingMembers, selfIds.pni)
|
||||
}
|
||||
}
|
||||
|
||||
if (selfPending.isPresent) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
selfInvitedToGroupUpdate = SelfInvitedToGroupUpdate(inviterAci = selfPending.get().addedByAci)
|
||||
)
|
||||
)
|
||||
return GroupChangeChatUpdate(updates = updates)
|
||||
}
|
||||
|
||||
if (decryptedGroupChange != null) {
|
||||
val foundingMemberUuid: ByteString = decryptedGroupChange.editorServiceIdBytes
|
||||
if (foundingMemberUuid.size > 0) {
|
||||
if (selfIds.matches(foundingMemberUuid)) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupCreationUpdate = GroupCreationUpdate(updaterAci = foundingMemberUuid)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupMemberAddedUpdate = GroupMemberAddedUpdate(updaterAci = foundingMemberUuid, newMemberAci = selfIds.aci.toByteString())
|
||||
)
|
||||
)
|
||||
}
|
||||
return GroupChangeChatUpdate(updates = updates)
|
||||
}
|
||||
}
|
||||
|
||||
if (group != null && DecryptedGroupUtil.findMemberByAci(group.members, selfIds.aci).isPresent) {
|
||||
updates.add(GroupChangeChatUpdate.Update(groupMemberJoinedUpdate = GroupMemberJoinedUpdate(newMemberAci = selfIds.aci.toByteString())))
|
||||
}
|
||||
return GroupChangeChatUpdate(updates = updates)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateDecryptedChangeUpdate(selfIds: ServiceIds, groupContext: DecryptedGroupV2Context): GroupChangeChatUpdate {
|
||||
var previousGroupState = groupContext.previousGroupState
|
||||
val change = groupContext.change!!
|
||||
if (DecryptedGroup().equals(previousGroupState)) {
|
||||
previousGroupState = null
|
||||
}
|
||||
val updates: MutableList<GroupChangeChatUpdate.Update> = LinkedList()
|
||||
var editorUnknown = change.editorServiceIdBytes.size == 0
|
||||
val editorServiceId = if (editorUnknown) null else parseOrNull(change.editorServiceIdBytes)
|
||||
if (editorServiceId == null || editorServiceId.isUnknown) {
|
||||
editorUnknown = true
|
||||
}
|
||||
translateMemberAdditions(change, editorUnknown, updates)
|
||||
translateModifyMemberRoles(change, editorUnknown, updates)
|
||||
translateInvitations(selfIds, change, editorUnknown, updates)
|
||||
translateRevokedInvitations(selfIds, change, editorUnknown, updates)
|
||||
translatePromotePending(selfIds, change, editorUnknown, updates)
|
||||
translateNewTitle(change, editorUnknown, updates)
|
||||
translateNewDescription(change, editorUnknown, updates)
|
||||
translateNewAvatar(change, editorUnknown, updates)
|
||||
translateNewTimer(change, editorUnknown, updates)
|
||||
translateNewAttributeAccess(change, editorUnknown, updates)
|
||||
translateNewMembershipAccess(change, editorUnknown, updates)
|
||||
translateNewGroupInviteLinkAccess(previousGroupState, change, editorUnknown, updates)
|
||||
translateRequestingMembers(selfIds, change, editorUnknown, updates)
|
||||
translateRequestingMemberApprovals(selfIds, change, editorUnknown, updates)
|
||||
translateRequestingMemberDeletes(selfIds, change, editorUnknown, updates)
|
||||
translateAnnouncementGroupChange(change, editorUnknown, updates)
|
||||
translatePromotePendingPniAci(selfIds, change, editorUnknown, updates)
|
||||
translateMemberRemovals(selfIds, change, editorUnknown, updates)
|
||||
if (updates.isEmpty()) {
|
||||
translateUnknownChange(change, editorUnknown, updates)
|
||||
}
|
||||
return GroupChangeChatUpdate(updates = updates)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateMemberAdditions(change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
for (member in change.newMembers) {
|
||||
if (!editorUnknown && member.aciBytes == change.editorServiceIdBytes) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupMemberJoinedByLinkUpdate = GroupMemberJoinedByLinkUpdate(newMemberAci = member.aciBytes)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupMemberAddedUpdate = GroupMemberAddedUpdate(
|
||||
updaterAci = if (editorUnknown) null else change.editorServiceIdBytes,
|
||||
newMemberAci = member.aciBytes,
|
||||
hadOpenInvitation = false
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateModifyMemberRoles(change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
for (roleChange in change.modifyMemberRoles) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupAdminStatusUpdate = GroupAdminStatusUpdate(
|
||||
updaterAci = if (editorUnknown) null else change.editorServiceIdBytes,
|
||||
memberAci = roleChange.aciBytes,
|
||||
wasAdminStatusGranted = roleChange.role == Member.Role.ADMINISTRATOR
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateInvitations(selfIds: ServiceIds, change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
val editorIsYou = selfIds.matches(change.editorServiceIdBytes)
|
||||
|
||||
var notYouInviteCount = 0
|
||||
for (invitee in change.newPendingMembers) {
|
||||
val newMemberIsYou = selfIds.matches(invitee.serviceIdBytes)
|
||||
if (newMemberIsYou) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
selfInvitedToGroupUpdate = SelfInvitedToGroupUpdate(
|
||||
inviterAci = if (editorUnknown) convertUnknownUUIDtoNull(invitee.addedByAci) else change.editorServiceIdBytes
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
if (editorIsYou) {
|
||||
updates.add(GroupChangeChatUpdate.Update(selfInvitedOtherUserToGroupUpdate = SelfInvitedOtherUserToGroupUpdate(inviteeServiceId = invitee.serviceIdBytes)))
|
||||
} else {
|
||||
notYouInviteCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
if (notYouInviteCount > 0) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupUnknownInviteeUpdate = GroupUnknownInviteeUpdate(
|
||||
inviterAci = if (editorUnknown) null else change.editorServiceIdBytes,
|
||||
inviteeCount = notYouInviteCount
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateRevokedInvitations(selfIds: ServiceIds, change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
val editorAci = if (editorUnknown) null else change.editorServiceIdBytes
|
||||
|
||||
val revokedInvitees = LinkedList<GroupInvitationRevokedUpdate.Invitee>()
|
||||
|
||||
for (invitee in change.deletePendingMembers) {
|
||||
val decline = invitee.serviceIdBytes == editorAci
|
||||
if (decline) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupInvitationDeclinedUpdate = GroupInvitationDeclinedUpdate(inviteeAci = invitee.serviceIdBytes)
|
||||
)
|
||||
)
|
||||
} else if (selfIds.matches(invitee.serviceIdBytes)) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupSelfInvitationRevokedUpdate = GroupSelfInvitationRevokedUpdate(revokerAci = editorAci)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
revokedInvitees.add(
|
||||
GroupInvitationRevokedUpdate.Invitee(
|
||||
inviteeAci = invitee.serviceIdBytes
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (revokedInvitees.isNotEmpty()) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupInvitationRevokedUpdate = GroupInvitationRevokedUpdate(
|
||||
updaterAci = editorAci,
|
||||
invitees = revokedInvitees
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translatePromotePending(selfIds: ServiceIds, change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
val editorAci = if (editorUnknown) null else change.editorServiceIdBytes
|
||||
val editorIsYou = if (editorUnknown) false else selfIds.matches(editorAci)
|
||||
|
||||
for (member in change.promotePendingMembers) {
|
||||
val newMemberIsYou: Boolean = selfIds.matches(member.aciBytes)
|
||||
if (editorIsYou) {
|
||||
if (newMemberIsYou) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupInvitationAcceptedUpdate = GroupInvitationAcceptedUpdate(
|
||||
inviterAci = null,
|
||||
newMemberAci = member.aciBytes
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupMemberAddedUpdate = GroupMemberAddedUpdate(
|
||||
updaterAci = editorAci,
|
||||
newMemberAci = member.aciBytes,
|
||||
hadOpenInvitation = true
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else if (editorUnknown) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupMemberJoinedUpdate = GroupMemberJoinedUpdate(
|
||||
newMemberAci = member.aciBytes
|
||||
)
|
||||
)
|
||||
)
|
||||
} else if (member.aciBytes == change.editorServiceIdBytes) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupInvitationAcceptedUpdate = GroupInvitationAcceptedUpdate(
|
||||
inviterAci = null,
|
||||
newMemberAci = member.aciBytes
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupMemberAddedUpdate = GroupMemberAddedUpdate(
|
||||
updaterAci = editorAci,
|
||||
newMemberAci = member.aciBytes,
|
||||
hadOpenInvitation = true
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateNewTitle(change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
if (change.newTitle != null) {
|
||||
val editorAci = if (editorUnknown) null else change.editorServiceIdBytes
|
||||
val newTitle = StringUtil.isolateBidi(change.newTitle?.value_)
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupNameUpdate = GroupNameUpdate(
|
||||
updaterAci = editorAci,
|
||||
newGroupName = newTitle
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateNewDescription(change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
if (change.newDescription != null) {
|
||||
val editorAci = if (editorUnknown) null else change.editorServiceIdBytes
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupDescriptionUpdate = GroupDescriptionUpdate(
|
||||
updaterAci = editorAci,
|
||||
newDescription = change.newDescription?.value_
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateNewAvatar(change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
if (change.newAvatar != null) {
|
||||
val editorAci = if (editorUnknown) null else change.editorServiceIdBytes
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupAvatarUpdate = GroupAvatarUpdate(
|
||||
updaterAci = editorAci,
|
||||
wasRemoved = change.newAvatar?.value_.isNullOrEmpty()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateNewTimer(change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
if (change.newTimer != null) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupExpirationTimerUpdate = GroupExpirationTimerUpdate(
|
||||
expiresInMs = change.newTimer!!.duration * 1000,
|
||||
updaterAci = if (editorUnknown) null else change.editorServiceIdBytes
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateNewAttributeAccess(change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
if (change.newAttributeAccess != AccessControl.AccessRequired.UNKNOWN) {
|
||||
val editorAci = if (editorUnknown) null else change.editorServiceIdBytes
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupAttributesAccessLevelChangeUpdate = GroupAttributesAccessLevelChangeUpdate(
|
||||
updaterAci = editorAci,
|
||||
accessLevel = translateGv2AccessLevel(change.newAttributeAccess)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateGv2AccessLevel(accessRequired: AccessRequired): GroupV2AccessLevel {
|
||||
return when (accessRequired) {
|
||||
AccessRequired.ANY -> GroupV2AccessLevel.ANY
|
||||
AccessRequired.MEMBER -> GroupV2AccessLevel.MEMBER
|
||||
AccessRequired.ADMINISTRATOR -> GroupV2AccessLevel.ADMINISTRATOR
|
||||
AccessRequired.UNSATISFIABLE -> GroupV2AccessLevel.UNSATISFIABLE
|
||||
AccessRequired.UNKNOWN -> GroupV2AccessLevel.UNKNOWN
|
||||
else -> GroupV2AccessLevel.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateNewMembershipAccess(change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
if (change.newMemberAccess !== AccessRequired.UNKNOWN) {
|
||||
val editorAci = if (editorUnknown) null else change.editorServiceIdBytes
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupMembershipAccessLevelChangeUpdate = GroupMembershipAccessLevelChangeUpdate(
|
||||
updaterAci = editorAci,
|
||||
accessLevel = translateGv2AccessLevel(change.newMemberAccess)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateNewGroupInviteLinkAccess(previousGroupState: DecryptedGroup?, change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
var previousAccessControl: AccessRequired? = null
|
||||
|
||||
if (previousGroupState?.accessControl != null) {
|
||||
previousAccessControl = previousGroupState.accessControl!!.addFromInviteLink
|
||||
}
|
||||
|
||||
var groupLinkEnabled = false
|
||||
val editorAci = if (editorUnknown) null else change.editorServiceIdBytes
|
||||
|
||||
when (change.newInviteLinkAccess) {
|
||||
AccessRequired.ANY -> {
|
||||
groupLinkEnabled = true
|
||||
updates.add(
|
||||
if (previousAccessControl == AccessControl.AccessRequired.ADMINISTRATOR) {
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupInviteLinkAdminApprovalUpdate = GroupInviteLinkAdminApprovalUpdate(
|
||||
updaterAci = editorAci,
|
||||
linkRequiresAdminApproval = false
|
||||
)
|
||||
)
|
||||
} else {
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupInviteLinkEnabledUpdate = GroupInviteLinkEnabledUpdate(
|
||||
updaterAci = editorAci,
|
||||
linkRequiresAdminApproval = false
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
AccessRequired.ADMINISTRATOR -> {
|
||||
groupLinkEnabled = true
|
||||
updates.add(
|
||||
if (previousAccessControl == AccessControl.AccessRequired.ANY) {
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupInviteLinkAdminApprovalUpdate = GroupInviteLinkAdminApprovalUpdate(
|
||||
updaterAci = editorAci,
|
||||
linkRequiresAdminApproval = true
|
||||
)
|
||||
)
|
||||
} else {
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupInviteLinkEnabledUpdate = GroupInviteLinkEnabledUpdate(
|
||||
updaterAci = editorAci,
|
||||
linkRequiresAdminApproval = true
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
AccessRequired.UNSATISFIABLE -> {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupInviteLinkDisabledUpdate = GroupInviteLinkDisabledUpdate(
|
||||
updaterAci = editorAci
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
if (!groupLinkEnabled && change.newInviteLinkPassword.size > 0) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupInviteLinkResetUpdate = GroupInviteLinkResetUpdate(
|
||||
updaterAci = editorAci
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateRequestingMembers(selfIds: ServiceIds, change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
val deleteRequestingUuids: Set<ByteString> = HashSet(change.deleteRequestingMembers)
|
||||
for (member in change.newRequestingMembers) {
|
||||
val requestingMemberIsYou = selfIds.matches(member.aciBytes)
|
||||
if (!requestingMemberIsYou && deleteRequestingUuids.contains(member.aciBytes)) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupSequenceOfRequestsAndCancelsUpdate = GroupSequenceOfRequestsAndCancelsUpdate(
|
||||
requestorAci = member.aciBytes,
|
||||
count = change.deleteRequestingMembers.size
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupJoinRequestUpdate = GroupJoinRequestUpdate(
|
||||
requestorAci = member.aciBytes
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateRequestingMemberApprovals(selfIds: ServiceIds, change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
val editorAci = if (editorUnknown) null else change.editorServiceIdBytes
|
||||
for (requestingMember in change.promoteRequestingMembers) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupJoinRequestApprovalUpdate = GroupJoinRequestApprovalUpdate(
|
||||
updaterAci = editorAci,
|
||||
requestorAci = requestingMember.aciBytes,
|
||||
wasApproved = true
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateRequestingMemberDeletes(selfIds: ServiceIds, change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
val newRequestingUuids = change.newRequestingMembers.stream().map { m: DecryptedRequestingMember -> m.aciBytes }.collect(Collectors.toSet())
|
||||
|
||||
val editorIsYou = selfIds.matches(change.editorServiceIdBytes)
|
||||
val editorAci = if (editorUnknown) null else change.editorServiceIdBytes
|
||||
for (requestingMember in change.deleteRequestingMembers) {
|
||||
if (newRequestingUuids.contains(requestingMember)) {
|
||||
continue
|
||||
}
|
||||
|
||||
val requestingMemberIsYou = selfIds.matches(requestingMember)
|
||||
if ((requestingMemberIsYou && editorIsYou) || (change.editorServiceIdBytes == requestingMember)) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupJoinRequestCanceledUpdate = GroupJoinRequestCanceledUpdate(
|
||||
requestorAci = requestingMember
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupJoinRequestApprovalUpdate = GroupJoinRequestApprovalUpdate(
|
||||
requestorAci = requestingMember,
|
||||
updaterAci = editorAci,
|
||||
wasApproved = false
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateAnnouncementGroupChange(change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
if (change.newIsAnnouncementGroup == EnabledState.ENABLED || change.newIsAnnouncementGroup == EnabledState.DISABLED) {
|
||||
val editorAci = if (editorUnknown) null else change.editorServiceIdBytes
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupAnnouncementOnlyChangeUpdate = GroupAnnouncementOnlyChangeUpdate(
|
||||
updaterAci = editorAci,
|
||||
isAnnouncementOnly = change.newIsAnnouncementGroup == EnabledState.ENABLED
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translatePromotePendingPniAci(selfIds: ServiceIds, change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
val editorIsYou = selfIds.matches(change.editorServiceIdBytes)
|
||||
for (newMember in change.promotePendingPniAciMembers) {
|
||||
if (editorUnknown) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupMemberJoinedUpdate = GroupMemberJoinedUpdate(
|
||||
newMemberAci = newMember.aciBytes
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
if ((selfIds.matches(newMember.aciBytes) && editorIsYou) || newMember.aciBytes == change.editorServiceIdBytes) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupInvitationAcceptedUpdate = GroupInvitationAcceptedUpdate(
|
||||
inviterAci = null,
|
||||
newMemberAci = newMember.aciBytes
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupMemberAddedUpdate = GroupMemberAddedUpdate(
|
||||
newMemberAci = newMember.aciBytes,
|
||||
updaterAci = change.editorServiceIdBytes,
|
||||
hadOpenInvitation = true,
|
||||
inviterAci = null
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateMemberRemovals(selfIds: ServiceIds, change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
val editorIsYou: Boolean = selfIds.matches(change.editorServiceIdBytes)
|
||||
for (member in change.deleteMembers) {
|
||||
val removedMemberIsYou: Boolean = selfIds.matches(member)
|
||||
if ((editorIsYou && removedMemberIsYou) || member == change.editorServiceIdBytes) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupMemberLeftUpdate = GroupMemberLeftUpdate(aci = member)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
groupMemberRemovedUpdate = GroupMemberRemovedUpdate(
|
||||
removerAci = if (editorUnknown) null else change.editorServiceIdBytes,
|
||||
removedAci = member
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun translateUnknownChange(change: DecryptedGroupChange, editorUnknown: Boolean, updates: MutableList<GroupChangeChatUpdate.Update>) {
|
||||
updates.add(
|
||||
GroupChangeChatUpdate.Update(
|
||||
genericGroupUpdate = GenericGroupUpdate(
|
||||
updaterAci = if (editorUnknown) null else change.editorServiceIdBytes
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertUnknownUUIDtoNull(id: ByteString?): ByteString? {
|
||||
if (id.isNullOrEmpty()) return null
|
||||
val uuid = UuidUtil.fromByteStringOrUnknown(id)
|
||||
|
||||
if (UuidUtil.UNKNOWN_UUID == uuid) return null
|
||||
return id
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.GroupAvatarUpdate;
|
|||
import org.thoughtcrime.securesms.backup.v2.proto.GroupChangeChatUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupCreationUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupDescriptionUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupExpirationTimerUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationAcceptedUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationDeclinedUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationRevokedUpdate;
|
||||
|
@ -44,11 +45,13 @@ import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestApprovalUpdate
|
|||
import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestCanceledUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberAddedUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberJoinedByLinkUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberJoinedUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberLeftUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberRemovedUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMembershipAccessLevelChangeUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupNameUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupSelfInvitationRevokedUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupUnknownInviteeUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2AccessLevel;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationDroppedMembersUpdate;
|
||||
|
@ -57,6 +60,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationSelfInvitedUpd
|
|||
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedOtherUserToGroupUpdate;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedToGroupUpdate;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription;
|
||||
import org.thoughtcrime.securesms.groups.GV2AccessLevelUtil;
|
||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
||||
|
@ -145,6 +149,9 @@ final class GroupsV2UpdateMessageProducer {
|
|||
for (GroupChangeChatUpdate.Update update : groupUpdates) {
|
||||
describeUpdate(update, updates);
|
||||
}
|
||||
if (updates.isEmpty()) {
|
||||
updates.add(updateDescription(context.getString(R.string.MessageRecord_group_updated), R.drawable.ic_update_group_16));
|
||||
}
|
||||
|
||||
return updates;
|
||||
}
|
||||
|
@ -210,6 +217,41 @@ final class GroupsV2UpdateMessageProducer {
|
|||
describeGroupV2MigrationInvitedMembersUpdate(update.groupV2MigrationInvitedMembersUpdate, updates);
|
||||
} else if (update.groupV2MigrationSelfInvitedUpdate != null) {
|
||||
describeGroupV2MigrationSelfInvitedUpdate(update.groupV2MigrationSelfInvitedUpdate, updates);
|
||||
} else if (update.groupMemberJoinedByLinkUpdate != null) {
|
||||
describeGroupMemberJoinedByLinkUpdate(update.groupMemberJoinedByLinkUpdate, updates);
|
||||
} else if (update.groupExpirationTimerUpdate != null) {
|
||||
describeGroupExpirationTimerUpdate(update.groupExpirationTimerUpdate, updates);
|
||||
} else if (update.groupSelfInvitationRevokedUpdate != null) {
|
||||
describeGroupSelfInvitationRevokedUpdate(update.groupSelfInvitationRevokedUpdate, updates);
|
||||
}
|
||||
}
|
||||
|
||||
private void describeGroupSelfInvitationRevokedUpdate(@NonNull GroupSelfInvitationRevokedUpdate update, @NonNull List<UpdateDescription> updates) {
|
||||
if (update.revokerAci == null) {
|
||||
updates.add(updateDescription(context.getString(R.string.MessageRecord_an_admin_revoked_your_invitation_to_the_group), R.drawable.ic_update_group_decline_16));
|
||||
} else {
|
||||
updates.add(updateDescription(R.string.MessageRecord_s_revoked_your_invitation_to_the_group, update.revokerAci, R.drawable.ic_update_group_decline_16));
|
||||
}
|
||||
}
|
||||
private void describeGroupExpirationTimerUpdate(@NonNull GroupExpirationTimerUpdate update, @NonNull List<UpdateDescription> updates) {
|
||||
String time = ExpirationUtil.getExpirationDisplayValue(context, update.expiresInMs / 1000);
|
||||
if (update.updaterAci == null) {
|
||||
updates.add(updateDescription(context.getString(R.string.MessageRecord_disappearing_message_time_set_to_s, time), R.drawable.ic_update_timer_16));
|
||||
} else {
|
||||
boolean editorIsYou = selfIds.matches(update.updaterAci);
|
||||
if (editorIsYou) {
|
||||
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_set_disappearing_message_time_to_s, time), R.drawable.ic_update_timer_16));
|
||||
} else {
|
||||
updates.add(updateDescription(R.string.MessageRecord_s_set_disappearing_message_time_to_s, update.updaterAci, time, R.drawable.ic_update_timer_16));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void describeGroupMemberJoinedByLinkUpdate(@NonNull GroupMemberJoinedByLinkUpdate update, @NonNull List<UpdateDescription> updates) {
|
||||
if (selfIds.matches(update.newMemberAci)) {
|
||||
updates.add(0, updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group_via_the_group_link), R.drawable.ic_update_group_accept_16));
|
||||
} else {
|
||||
updates.add(updateDescription(R.string.MessageRecord_s_joined_the_group_via_the_group_link, update.newMemberAci, R.drawable.ic_update_group_accept_16));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,12 +296,10 @@ final class GroupsV2UpdateMessageProducer {
|
|||
}
|
||||
|
||||
private void describeInviteLinkDisabledUpdate(@NonNull GroupInviteLinkDisabledUpdate update, @NonNull List<UpdateDescription> updates) {
|
||||
boolean editorIsYou = selfIds.matches(update.updaterAci);
|
||||
|
||||
if (update.updaterAci == null) {
|
||||
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_off), R.drawable.ic_update_group_role_16));
|
||||
} else {
|
||||
if (editorIsYou) {
|
||||
if (selfIds.matches(update.updaterAci)) {
|
||||
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_off_the_group_link), R.drawable.ic_update_group_role_16));
|
||||
} else {
|
||||
updates.add(updateDescription(R.string.MessageRecord_s_turned_off_the_group_link, update.updaterAci, R.drawable.ic_update_group_role_16));
|
||||
|
@ -268,7 +308,6 @@ final class GroupsV2UpdateMessageProducer {
|
|||
}
|
||||
|
||||
private void describeInviteLinkEnabledUpdate(@NonNull GroupInviteLinkEnabledUpdate update, @NonNull List<UpdateDescription> updates) {
|
||||
boolean editorIsYou = selfIds.matches(update.updaterAci);
|
||||
|
||||
if (update.updaterAci == null) {
|
||||
if (update.linkRequiresAdminApproval) {
|
||||
|
@ -277,7 +316,7 @@ final class GroupsV2UpdateMessageProducer {
|
|||
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_off), R.drawable.ic_update_group_role_16));
|
||||
}
|
||||
} else {
|
||||
if (editorIsYou) {
|
||||
if (selfIds.matches(update.updaterAci)) {
|
||||
if (update.linkRequiresAdminApproval) {
|
||||
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_on), R.drawable.ic_update_group_role_16));
|
||||
} else {
|
||||
|
@ -366,7 +405,7 @@ final class GroupsV2UpdateMessageProducer {
|
|||
private void describeGroupInvitationRevokedUpdate(@NonNull GroupInvitationRevokedUpdate update, @NonNull List<UpdateDescription> updates) {
|
||||
int revokedMeCount = 0;
|
||||
for (GroupInvitationRevokedUpdate.Invitee invitee : update.invitees) {
|
||||
if (selfIds.matches(invitee.inviteeAci) || selfIds.matches(invitee.inviteePni)) {
|
||||
if ((invitee.inviteeAci != null && selfIds.matches(invitee.inviteeAci)) || (invitee.inviteePni != null && selfIds.matches(invitee.inviteePni))) {
|
||||
revokedMeCount++;
|
||||
}
|
||||
}
|
||||
|
@ -409,20 +448,24 @@ final class GroupsV2UpdateMessageProducer {
|
|||
} else {
|
||||
updates.add(updateDescription(R.string.MessageRecord_s_joined_the_group, update.newMemberAci, R.drawable.ic_update_group_add_16));
|
||||
}
|
||||
} else if (update.hadOpenInvitation) {
|
||||
if (selfIds.matches(update.updaterAci)) {
|
||||
updates.add(updateDescription(R.string.MessageRecord_you_added_invited_member_s, update.newMemberAci, R.drawable.ic_update_group_add_16));
|
||||
} else {
|
||||
updates.add(updateDescription(R.string.MessageRecord_s_added_invited_member_s, update.updaterAci, update.newMemberAci, R.drawable.ic_update_group_add_16));
|
||||
}
|
||||
} else {
|
||||
if (newMemberIsYou) {
|
||||
updates.add(0, updateDescription(R.string.MessageRecord_s_added_you, update.updaterAci, R.drawable.ic_update_group_add_16));
|
||||
} else if (selfIds.matches(update.updaterAci)) {
|
||||
if (update.hadOpenInvitation) {
|
||||
updates.add(updateDescription(R.string.MessageRecord_you_added_invited_member_s, update.newMemberAci, R.drawable.ic_update_group_add_16));
|
||||
} else {
|
||||
updates.add(updateDescription(R.string.MessageRecord_you_added_s, update.newMemberAci, R.drawable.ic_update_group_add_16));
|
||||
}
|
||||
} else {
|
||||
if (update.hadOpenInvitation) {
|
||||
updates.add(updateDescription(R.string.MessageRecord_s_added_invited_member_s, update.updaterAci, update.newMemberAci, R.drawable.ic_update_group_add_16));
|
||||
} else {
|
||||
updates.add(updateDescription(R.string.MessageRecord_s_added_s, update.updaterAci, update.newMemberAci, R.drawable.ic_update_group_add_16));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void describeGroupMemberJoinedUpdate(@NonNull GroupMemberJoinedUpdate update, @NonNull List<UpdateDescription> updates) {
|
||||
boolean newMemberIsYou = selfIds.matches(update.newMemberAci);
|
||||
|
|
|
@ -180,7 +180,11 @@ public abstract class MessageRecord extends DisplayRecord {
|
|||
|
||||
public @Nullable UpdateDescription getUpdateDisplayBody(@NonNull Context context, @Nullable Consumer<RecipientId> recipientClickHandler) {
|
||||
if (isGroupUpdate() && isGroupV2()) {
|
||||
if (messageExtras != null) {
|
||||
return getGv2ChangeDescription(context, messageExtras, recipientClickHandler);
|
||||
} else {
|
||||
return getGv2ChangeDescription(context, getBody(), recipientClickHandler);
|
||||
}
|
||||
} else if (isGroupUpdate() && isOutgoing()) {
|
||||
return staticUpdateDescription(context.getString(R.string.MessageRecord_you_updated_group), R.drawable.ic_update_group_16);
|
||||
} else if (isGroupUpdate()) {
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase;
|
|||
import org.thoughtcrime.securesms.database.ThreadTable;
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.v2.GroupCandidateHelper;
|
||||
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
|
||||
|
@ -1297,8 +1298,8 @@ final class GroupManagerV2 {
|
|||
{
|
||||
GroupId.V2 groupId = GroupId.v2(masterKey);
|
||||
Recipient groupRecipient = Recipient.externalGroupExact(groupId);
|
||||
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, groupMutation, signedGroupChange);
|
||||
OutgoingMessage outgoingMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, decryptedGroupV2Context, System.currentTimeMillis());
|
||||
GV2UpdateDescription updateDescription = GroupProtoUtil.createOutgoingGroupV2UpdateDescription(masterKey, groupMutation, signedGroupChange);
|
||||
OutgoingMessage outgoingMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, System.currentTimeMillis());
|
||||
|
||||
|
||||
DecryptedGroupChange plainGroupChange = groupMutation.getGroupChange();
|
||||
|
|
|
@ -10,7 +10,13 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
|||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupChangeChatUpdate;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.GroupsV2UpdateMessageConverter;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup;
|
||||
|
@ -45,6 +51,19 @@ public final class GroupProtoUtil {
|
|||
throw new GroupNotAMemberException();
|
||||
}
|
||||
|
||||
public static GV2UpdateDescription createOutgoingGroupV2UpdateDescription(@NonNull GroupMasterKey masterKey,
|
||||
@NonNull GroupMutation groupMutation,
|
||||
@Nullable GroupChange signedServerChange)
|
||||
{
|
||||
DecryptedGroupV2Context groupV2Context = createDecryptedGroupV2Context(masterKey, groupMutation, signedServerChange);
|
||||
GroupChangeChatUpdate update = GroupsV2UpdateMessageConverter.translateDecryptedChange(SignalStore.account().getServiceIds(), groupV2Context);
|
||||
|
||||
return new GV2UpdateDescription.Builder()
|
||||
.gv2ChangeDescription(groupV2Context)
|
||||
.groupChangeUpdate(update)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static DecryptedGroupV2Context createDecryptedGroupV2Context(@NonNull GroupMasterKey masterKey,
|
||||
@NonNull GroupMutation groupMutation,
|
||||
@Nullable GroupChange signedServerChange)
|
||||
|
|
|
@ -18,13 +18,16 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
|||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupChangeChatUpdate;
|
||||
import org.thoughtcrime.securesms.database.GroupTable;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadTable;
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord;
|
||||
import org.thoughtcrime.securesms.database.model.GroupsV2UpdateMessageConverter;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupDoesNotExistException;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
|
@ -573,8 +576,8 @@ public class GroupsV2StateProcessor {
|
|||
.deleteMembers(Collections.singletonList(serviceIds.getAci().toByteString()))
|
||||
.build();
|
||||
|
||||
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(decryptedGroup, simulatedGroupChange, simulatedGroupState), null);
|
||||
OutgoingMessage leaveMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, decryptedGroupV2Context, System.currentTimeMillis());
|
||||
GV2UpdateDescription updateDescription = GroupProtoUtil.createOutgoingGroupV2UpdateDescription(masterKey, new GroupMutation(decryptedGroup, simulatedGroupChange, simulatedGroupState), null);
|
||||
OutgoingMessage leaveMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, System.currentTimeMillis());
|
||||
|
||||
try {
|
||||
MessageTable mmsDatabase = SignalDatabase.messages();
|
||||
|
@ -803,13 +806,18 @@ public class GroupsV2StateProcessor {
|
|||
|
||||
boolean outgoing = !editor.isPresent() || aci.equals(editor.get());
|
||||
|
||||
GV2UpdateDescription updateDescription = new GV2UpdateDescription.Builder()
|
||||
.gv2ChangeDescription(decryptedGroupV2Context)
|
||||
.groupChangeUpdate(GroupsV2UpdateMessageConverter.translateDecryptedChange(SignalStore.account().getServiceIds(), decryptedGroupV2Context))
|
||||
.build();
|
||||
|
||||
if (outgoing) {
|
||||
try {
|
||||
MessageTable mmsDatabase = SignalDatabase.messages();
|
||||
ThreadTable threadTable = SignalDatabase.threads();
|
||||
RecipientId recipientId = recipientTable.getOrInsertFromGroupId(groupId);
|
||||
Recipient recipient = Recipient.resolved(recipientId);
|
||||
OutgoingMessage outgoingMessage = OutgoingMessage.groupUpdateMessage(recipient, decryptedGroupV2Context, timestamp);
|
||||
OutgoingMessage outgoingMessage = OutgoingMessage.groupUpdateMessage(recipient, updateDescription, timestamp);
|
||||
long threadId = threadTable.getOrCreateThreadIdFor(recipient);
|
||||
long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null);
|
||||
|
||||
|
|
|
@ -8,7 +8,9 @@ import org.thoughtcrime.securesms.database.model.ParentStoryId
|
|||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
@ -36,7 +38,8 @@ class IncomingMessage(
|
|||
sharedContacts: List<Contact> = emptyList(),
|
||||
linkPreviews: List<LinkPreview> = emptyList(),
|
||||
mentions: List<Mention> = emptyList(),
|
||||
val giftBadge: GiftBadge? = null
|
||||
val giftBadge: GiftBadge? = null,
|
||||
val messageExtras: MessageExtras? = null
|
||||
) {
|
||||
|
||||
val attachments: List<Attachment> = ArrayList(attachments)
|
||||
|
@ -104,9 +107,8 @@ class IncomingMessage(
|
|||
serverTimeMillis = timestamp,
|
||||
groupId = groupId,
|
||||
groupContext = messageGroupContext,
|
||||
serverGuid = serverGuid,
|
||||
body = messageGroupContext.encodedGroupContext,
|
||||
type = MessageType.GROUP_UPDATE
|
||||
type = MessageType.GROUP_UPDATE,
|
||||
messageExtras = MessageExtras(gv2UpdateDescription = GV2UpdateDescription(gv2ChangeDescription = groupContext, groupChangeUpdate = null))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
|||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras;
|
||||
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
@ -30,7 +31,6 @@ import java.util.List;
|
|||
*/
|
||||
public final class MessageGroupContext {
|
||||
|
||||
@NonNull private final String encodedGroupContext;
|
||||
@NonNull private final GroupProperties group;
|
||||
@Nullable private final GroupV1Properties groupV1;
|
||||
@Nullable private final GroupV2Properties groupV2;
|
||||
|
@ -38,7 +38,6 @@ public final class MessageGroupContext {
|
|||
public MessageGroupContext(@NonNull String encodedGroupContext, boolean v2)
|
||||
throws IOException
|
||||
{
|
||||
this.encodedGroupContext = encodedGroupContext;
|
||||
if (v2) {
|
||||
this.groupV1 = null;
|
||||
this.groupV2 = new GroupV2Properties(DecryptedGroupV2Context.ADAPTER.decode(Base64.decode(encodedGroupContext)));
|
||||
|
@ -50,15 +49,25 @@ public final class MessageGroupContext {
|
|||
}
|
||||
}
|
||||
|
||||
public MessageGroupContext(@NonNull MessageExtras messageExtras, boolean v2) {
|
||||
if (v2) {
|
||||
this.groupV1 = null;
|
||||
this.groupV2 = new GroupV2Properties(messageExtras.gv2UpdateDescription.gv2ChangeDescription);
|
||||
this.group = groupV2;
|
||||
} else {
|
||||
this.groupV1 = new GroupV1Properties(messageExtras.gv1Context);
|
||||
this.groupV2 = null;
|
||||
this.group = groupV1;
|
||||
}
|
||||
}
|
||||
|
||||
public MessageGroupContext(@NonNull GroupContext group) {
|
||||
this.encodedGroupContext = Base64.encodeWithPadding(group.encode());
|
||||
this.groupV1 = new GroupV1Properties(group);
|
||||
this.groupV2 = null;
|
||||
this.group = groupV1;
|
||||
}
|
||||
|
||||
public MessageGroupContext(@NonNull DecryptedGroupV2Context group) {
|
||||
this.encodedGroupContext = Base64.encodeWithPadding(group.encode());
|
||||
this.groupV1 = null;
|
||||
this.groupV2 = new GroupV2Properties(group);
|
||||
this.group = groupV2;
|
||||
|
@ -82,10 +91,6 @@ public final class MessageGroupContext {
|
|||
return groupV2 != null;
|
||||
}
|
||||
|
||||
public @NonNull String getEncodedGroupContext() {
|
||||
return encodedGroupContext;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return group.getName();
|
||||
}
|
||||
|
|
|
@ -9,8 +9,9 @@ import org.thoughtcrime.securesms.database.model.Mention
|
|||
import org.thoughtcrime.securesms.database.model.ParentStoryId
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.sms.GroupV2UpdateMessageUtil
|
||||
|
@ -52,7 +53,8 @@ data class OutgoingMessage(
|
|||
val scheduledDate: Long = -1,
|
||||
val messageToEdit: Long = 0,
|
||||
val isReportSpam: Boolean = false,
|
||||
val isMessageRequestAccept: Boolean = false
|
||||
val isMessageRequestAccept: Boolean = false,
|
||||
val messageExtras: MessageExtras? = null
|
||||
) {
|
||||
|
||||
val isV2Group: Boolean = messageGroupContext != null && GroupV2UpdateMessageUtil.isGroupV2(messageGroupContext)
|
||||
|
@ -228,17 +230,18 @@ data class OutgoingMessage(
|
|||
* Helper for creating a group update message when a state change occurs and needs to be sent to others.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun groupUpdateMessage(threadRecipient: Recipient, group: DecryptedGroupV2Context, sentTimeMillis: Long): OutgoingMessage {
|
||||
val groupContext = MessageGroupContext(group)
|
||||
fun groupUpdateMessage(threadRecipient: Recipient, update: GV2UpdateDescription, sentTimeMillis: Long): OutgoingMessage {
|
||||
val messageExtras = MessageExtras(gv2UpdateDescription = update)
|
||||
val groupContext = MessageGroupContext(update.gv2ChangeDescription!!)
|
||||
|
||||
return OutgoingMessage(
|
||||
threadRecipient = threadRecipient,
|
||||
body = groupContext.encodedGroupContext,
|
||||
sentTimeMillis = sentTimeMillis,
|
||||
messageGroupContext = groupContext,
|
||||
isGroup = true,
|
||||
isGroupUpdate = true,
|
||||
isSecure = true
|
||||
isSecure = true,
|
||||
messageExtras = messageExtras
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -260,7 +263,6 @@ data class OutgoingMessage(
|
|||
): OutgoingMessage {
|
||||
return OutgoingMessage(
|
||||
threadRecipient = threadRecipient,
|
||||
body = groupContext.encodedGroupContext,
|
||||
isGroup = true,
|
||||
isGroupUpdate = true,
|
||||
messageGroupContext = groupContext,
|
||||
|
|
|
@ -52,7 +52,7 @@ message AccountData {
|
|||
bool linkPreviews = 5;
|
||||
bool notDiscoverableByPhoneNumber = 6;
|
||||
bool preferContactAvatars = 7;
|
||||
uint32 universalExpireTimer = 8;
|
||||
uint32 universalExpireTimer = 8; // 0 means no universal expire timer.
|
||||
repeated string preferredReactionEmoji = 9;
|
||||
bool displayBadgesOnProfile = 10;
|
||||
bool keepMutedChatsArchived = 11;
|
||||
|
@ -132,7 +132,7 @@ message Chat {
|
|||
uint64 recipientId = 2;
|
||||
bool archived = 3;
|
||||
uint32 pinnedOrder = 4; // 0 = unpinned, otherwise chat is considered pinned and will be displayed in ascending order
|
||||
uint64 expirationTimerMs = 5;
|
||||
uint64 expirationTimerMs = 5; // 0 = no expire timer.
|
||||
uint64 muteUntilMs = 6;
|
||||
bool markedUnread = 7;
|
||||
bool dontNotifyForMentionsIfMuted = 8;
|
||||
|
@ -537,8 +537,10 @@ message SimpleChatUpdate {
|
|||
Type type = 1;
|
||||
}
|
||||
|
||||
// For 1:1 chat updates only.
|
||||
// For group thread updates use GroupExpirationTimerUpdate.
|
||||
message ExpirationTimerChatUpdate {
|
||||
uint32 expiresInMs = 1;
|
||||
uint32 expiresInMs = 1; // 0 means the expiration timer was disabled
|
||||
}
|
||||
|
||||
message ProfileChangeChatUpdate {
|
||||
|
@ -591,6 +593,7 @@ message GroupChangeChatUpdate {
|
|||
GroupV2MigrationInvitedMembersUpdate groupV2MigrationInvitedMembersUpdate = 31;
|
||||
GroupV2MigrationDroppedMembersUpdate groupV2MigrationDroppedMembersUpdate = 32;
|
||||
GroupSequenceOfRequestsAndCancelsUpdate groupSequenceOfRequestsAndCancelsUpdate = 33;
|
||||
GroupExpirationTimerUpdate groupExpirationTimerUpdate = 34;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -794,6 +797,12 @@ message GroupV2MigrationDroppedMembersUpdate {
|
|||
int32 droppedMembersCount = 1;
|
||||
}
|
||||
|
||||
// For 1:1 timer updates, use ExpirationTimerChatUpdate.
|
||||
message GroupExpirationTimerUpdate {
|
||||
uint32 expiresInMs = 1; // 0 means the expiration timer was disabled
|
||||
optional bytes updaterAci = 2;
|
||||
}
|
||||
|
||||
message StickerPack {
|
||||
bytes id = 1;
|
||||
bytes key = 2;
|
||||
|
|
|
@ -376,7 +376,10 @@ message ExternalLaunchTransactionState {
|
|||
}
|
||||
|
||||
message MessageExtras {
|
||||
oneof extra {
|
||||
GV2UpdateDescription gv2UpdateDescription = 1;
|
||||
signalservice.GroupContext gv1Context = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message GV2UpdateDescription {
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
|||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupChangeChatUpdate;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
@ -34,6 +36,7 @@ import org.whispersystems.signalservice.api.push.ServiceIds;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -62,6 +65,8 @@ public final class GroupsV2UpdateMessageProducerTest {
|
|||
private ACI alice;
|
||||
private ACI bob;
|
||||
|
||||
private ServiceIds selfIds;
|
||||
|
||||
private GroupsV2UpdateMessageProducer producer;
|
||||
|
||||
@Rule
|
||||
|
@ -79,6 +84,8 @@ public final class GroupsV2UpdateMessageProducerTest {
|
|||
alice = ACI.from(UUID.randomUUID());
|
||||
bob = ACI.from(UUID.randomUUID());
|
||||
|
||||
selfIds = new ServiceIds(you, PNI.from(UUID.randomUUID()));
|
||||
|
||||
recipientIdMockedStatic.when(() -> RecipientId.from(anyLong())).thenCallRealMethod();
|
||||
|
||||
RecipientId aliceId = RecipientId.from(1);
|
||||
|
@ -87,7 +94,7 @@ public final class GroupsV2UpdateMessageProducerTest {
|
|||
Recipient aliceRecipient = recipientWithName(aliceId, "Alice");
|
||||
Recipient bobRecipient = recipientWithName(bobId, "Bob");
|
||||
|
||||
producer = new GroupsV2UpdateMessageProducer(ApplicationProvider.getApplicationContext(), new ServiceIds(you, PNI.from(UUID.randomUUID())), null);
|
||||
producer = new GroupsV2UpdateMessageProducer(ApplicationProvider.getApplicationContext(), selfIds, null);
|
||||
|
||||
recipientIdMockedStatic.when(() -> RecipientId.from(alice)).thenReturn(aliceId);
|
||||
recipientIdMockedStatic.when(() -> RecipientId.from(bob)).thenReturn(bobId);
|
||||
|
@ -1422,6 +1429,27 @@ public final class GroupsV2UpdateMessageProducerTest {
|
|||
assertEquals("Alice said hello to Bob, and Bob said hello back to Alice.", result.toString());
|
||||
}
|
||||
|
||||
private @NonNull String describeConvertedNewGroup(@NonNull DecryptedGroup groupState, @NonNull DecryptedGroupChange groupChange) {
|
||||
GroupChangeChatUpdate update = GroupsV2UpdateMessageConverter.translateDecryptedChangeNewGroup(selfIds, new DecryptedGroupV2Context.Builder()
|
||||
.change(groupChange)
|
||||
.groupState(groupState)
|
||||
.build());
|
||||
|
||||
return producer.describeChanges(update.updates).get(0).getSpannable().toString();
|
||||
}
|
||||
|
||||
private @NonNull List<String> describeConvertedChange(@Nullable DecryptedGroup previousGroupState, @NonNull DecryptedGroupChange change) {
|
||||
GroupChangeChatUpdate update = GroupsV2UpdateMessageConverter.translateDecryptedChangeUpdate(selfIds, new DecryptedGroupV2Context.Builder()
|
||||
.change(change)
|
||||
.previousGroupState(previousGroupState)
|
||||
.build());
|
||||
|
||||
return Stream.of(producer.describeChanges(update.updates))
|
||||
.map(UpdateDescription::getSpannable)
|
||||
.map(Spannable::toString)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private @NonNull List<String> describeChange(@NonNull DecryptedGroupChange change) {
|
||||
return describeChange(null, change);
|
||||
}
|
||||
|
@ -1429,10 +1457,20 @@ public final class GroupsV2UpdateMessageProducerTest {
|
|||
private @NonNull List<String> describeChange(@Nullable DecryptedGroup previousGroupState,
|
||||
@NonNull DecryptedGroupChange change)
|
||||
{
|
||||
return Stream.of(producer.describeChanges(previousGroupState, change))
|
||||
List<String> convertedChange = describeConvertedChange(previousGroupState, change);
|
||||
List<String> describedChange = Stream.of(producer.describeChanges(previousGroupState, change))
|
||||
.map(UpdateDescription::getSpannable)
|
||||
.map(Spannable::toString)
|
||||
.toList();
|
||||
assertEquals(describedChange.size(), convertedChange.size());
|
||||
|
||||
ListIterator<String> convertedIterator = convertedChange.listIterator();
|
||||
ListIterator<String> describedIterator = describedChange.listIterator();
|
||||
|
||||
while (convertedIterator.hasNext()) {
|
||||
assertEquals(describedIterator.next(), convertedIterator.next());
|
||||
}
|
||||
return describedChange;
|
||||
}
|
||||
|
||||
private @NonNull String describeNewGroup(@NonNull DecryptedGroup group) {
|
||||
|
@ -1440,7 +1478,12 @@ public final class GroupsV2UpdateMessageProducerTest {
|
|||
}
|
||||
|
||||
private @NonNull String describeNewGroup(@NonNull DecryptedGroup group, @NonNull DecryptedGroupChange groupChange) {
|
||||
return producer.describeNewGroup(group, groupChange).getSpannable().toString();
|
||||
String newGroupString = producer.describeNewGroup(group, groupChange).getSpannable().toString();
|
||||
String convertedGroupString = describeConvertedNewGroup(group, groupChange);
|
||||
|
||||
assertEquals(newGroupString, convertedGroupString);
|
||||
|
||||
return newGroupString;
|
||||
}
|
||||
|
||||
private static GroupStateBuilder newGroupBy(ACI foundingMember, int revision) {
|
||||
|
|
Loading…
Add table
Reference in a new issue