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 com.annimon.stream.Stream
|
||||||
import okio.ByteString.Companion.toByteString
|
import okio.ByteString.Companion.toByteString
|
||||||
import org.signal.core.util.Base64
|
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.Base64.decodeOrThrow
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.signal.core.util.requireBlob
|
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.IdentityKeyMismatchSet
|
||||||
import org.thoughtcrime.securesms.database.documents.NetworkFailureSet
|
import org.thoughtcrime.securesms.database.documents.NetworkFailureSet
|
||||||
import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil
|
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.ReactionRecord
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
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.ProfileChangeDetails
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
|
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
|
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.mms.QuoteModel
|
import org.thoughtcrime.securesms.mms.QuoteModel
|
||||||
import org.thoughtcrime.securesms.util.JsonUtils
|
import org.thoughtcrime.securesms.util.JsonUtils
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
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) -> {
|
MessageTypes.isCallLog(record.type) -> {
|
||||||
val call = calls.getCallByMessageId(record.id)
|
val call = calls.getCallByMessageId(record.id)
|
||||||
if (call != null) {
|
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 {
|
private fun Cursor.toBackupMessageRecord(): BackupMessageRecord {
|
||||||
return BackupMessageRecord(
|
return BackupMessageRecord(
|
||||||
id = this.requireLong(MessageTable.ID),
|
id = this.requireLong(MessageTable.ID),
|
||||||
|
@ -443,7 +479,8 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
||||||
receiptTimestamp = this.requireLong(MessageTable.RECEIPT_TIMESTAMP),
|
receiptTimestamp = this.requireLong(MessageTable.RECEIPT_TIMESTAMP),
|
||||||
networkFailureRecipientIds = this.requireString(MessageTable.NETWORK_FAILURES).parseNetworkFailures(),
|
networkFailureRecipientIds = this.requireString(MessageTable.NETWORK_FAILURES).parseNetworkFailures(),
|
||||||
identityMismatchRecipientIds = this.requireString(MessageTable.MISMATCHED_IDENTITIES).parseIdentityMismatches(),
|
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 read: Boolean,
|
||||||
val networkFailureRecipientIds: Set<Long>,
|
val networkFailureRecipientIds: Set<Long>,
|
||||||
val identityMismatchRecipientIds: 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.NetworkFailure
|
||||||
import org.thoughtcrime.securesms.database.documents.NetworkFailureSet
|
import org.thoughtcrime.securesms.database.documents.NetworkFailureSet
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
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.ProfileChangeDetails
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
|
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
|
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
|
// Calls don't use the incoming/outgoing flags, so we overwrite the flags here
|
||||||
this.put(MessageTable.TYPE, typeFlags)
|
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)
|
this.put(MessageTable.TYPE, getAsLong(MessageTable.TYPE) or typeFlags)
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,8 @@ fun MessageTable.getMessagesForBackup(): ChatItemExportIterator {
|
||||||
MessageTable.READ,
|
MessageTable.READ,
|
||||||
MessageTable.NETWORK_FAILURES,
|
MessageTable.NETWORK_FAILURES,
|
||||||
MessageTable.MISMATCHED_IDENTITIES,
|
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)
|
.from(MessageTable.TABLE_NAME)
|
||||||
.where(
|
.where(
|
||||||
|
|
|
@ -2305,6 +2305,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||||
val parentStoryId = ParentStoryId.deserialize(cursor.requireLong(PARENT_STORY_ID))
|
val parentStoryId = ParentStoryId.deserialize(cursor.requireLong(PARENT_STORY_ID))
|
||||||
val messageRangesData = cursor.requireBlob(MESSAGE_RANGES)
|
val messageRangesData = cursor.requireBlob(MESSAGE_RANGES)
|
||||||
val scheduledDate = cursor.requireLong(SCHEDULED_DATE)
|
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 quoteId = cursor.requireLong(QUOTE_ID)
|
||||||
val quoteAuthor = cursor.requireLong(QUOTE_AUTHOR)
|
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))) {
|
if (body != null && (MessageTypes.isGroupQuit(outboxType) || MessageTypes.isGroupUpdate(outboxType))) {
|
||||||
OutgoingMessage.groupUpdateMessage(
|
OutgoingMessage.groupUpdateMessage(
|
||||||
threadRecipient = threadRecipient,
|
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,
|
avatar = attachments,
|
||||||
sentTimeMillis = timestamp,
|
sentTimeMillis = timestamp,
|
||||||
expiresIn = 0,
|
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(PARENT_STORY_ID, if (message.parentStoryId != null) message.parentStoryId.serialize() else 0)
|
||||||
contentValues.put(SCHEDULED_DATE, message.scheduledDate)
|
contentValues.put(SCHEDULED_DATE, message.scheduledDate)
|
||||||
contentValues.putNull(LATEST_REVISION_ID)
|
contentValues.putNull(LATEST_REVISION_ID)
|
||||||
|
contentValues.put(MESSAGE_EXTRAS, message.messageExtras?.encode())
|
||||||
|
|
||||||
if (editedMessage != null) {
|
if (editedMessage != null) {
|
||||||
contentValues.put(ORIGINAL_MESSAGE_ID, editedMessage.getOriginalOrOwnMessageId().id)
|
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 editCount = cursor.requireInt(REVISION_NUMBER)
|
||||||
val isRead = cursor.requireBoolean(READ)
|
val isRead = cursor.requireBoolean(READ)
|
||||||
val messageExtraBytes = cursor.requireBlob(MESSAGE_EXTRAS)
|
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)) {
|
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||||
hasReadReceipt = false
|
hasReadReceipt = false
|
||||||
|
|
|
@ -1929,7 +1929,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||||
|
|
||||||
val hasReadReceipt = TextSecurePreferences.isReadReceiptsEnabled(context) && cursor.requireBoolean(HAS_READ_RECEIPT)
|
val hasReadReceipt = TextSecurePreferences.isReadReceiptsEnabled(context) && cursor.requireBoolean(HAS_READ_RECEIPT)
|
||||||
val extraString = cursor.getString(cursor.getColumnIndexOrThrow(SNIPPET_EXTRAS))
|
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) {
|
val extra: Extra? = if (extraString != null) {
|
||||||
try {
|
try {
|
||||||
val jsonObject = SaneJSONObject(JSONObject(extraString))
|
val jsonObject = SaneJSONObject(JSONObject(extraString))
|
||||||
|
@ -1974,6 +1975,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||||
.setPinned(cursor.requireBoolean(PINNED))
|
.setPinned(cursor.requireBoolean(PINNED))
|
||||||
.setUnreadSelfMentionsCount(cursor.requireInt(UNREAD_SELF_MENTION_COUNT))
|
.setUnreadSelfMentionsCount(cursor.requireInt(UNREAD_SELF_MENTION_COUNT))
|
||||||
.setExtra(extra)
|
.setExtra(extra)
|
||||||
|
.setSnippetMessageExtras(messageExtras)
|
||||||
.build()
|
.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.GroupChangeChatUpdate;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupCreationUpdate;
|
import org.thoughtcrime.securesms.backup.v2.proto.GroupCreationUpdate;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupDescriptionUpdate;
|
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.GroupInvitationAcceptedUpdate;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationDeclinedUpdate;
|
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationDeclinedUpdate;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationRevokedUpdate;
|
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.GroupJoinRequestCanceledUpdate;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestUpdate;
|
import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestUpdate;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberAddedUpdate;
|
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.GroupMemberJoinedUpdate;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberLeftUpdate;
|
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberLeftUpdate;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberRemovedUpdate;
|
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberRemovedUpdate;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupMembershipAccessLevelChangeUpdate;
|
import org.thoughtcrime.securesms.backup.v2.proto.GroupMembershipAccessLevelChangeUpdate;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupNameUpdate;
|
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.GroupUnknownInviteeUpdate;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2AccessLevel;
|
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2AccessLevel;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationDroppedMembersUpdate;
|
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.GroupV2MigrationUpdate;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedOtherUserToGroupUpdate;
|
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedOtherUserToGroupUpdate;
|
||||||
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedToGroupUpdate;
|
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.database.model.databaseprotos.GV2UpdateDescription;
|
||||||
import org.thoughtcrime.securesms.groups.GV2AccessLevelUtil;
|
import org.thoughtcrime.securesms.groups.GV2AccessLevelUtil;
|
||||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
||||||
|
@ -145,6 +149,9 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
for (GroupChangeChatUpdate.Update update : groupUpdates) {
|
for (GroupChangeChatUpdate.Update update : groupUpdates) {
|
||||||
describeUpdate(update, updates);
|
describeUpdate(update, updates);
|
||||||
}
|
}
|
||||||
|
if (updates.isEmpty()) {
|
||||||
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_group_updated), R.drawable.ic_update_group_16));
|
||||||
|
}
|
||||||
|
|
||||||
return updates;
|
return updates;
|
||||||
}
|
}
|
||||||
|
@ -210,6 +217,41 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
describeGroupV2MigrationInvitedMembersUpdate(update.groupV2MigrationInvitedMembersUpdate, updates);
|
describeGroupV2MigrationInvitedMembersUpdate(update.groupV2MigrationInvitedMembersUpdate, updates);
|
||||||
} else if (update.groupV2MigrationSelfInvitedUpdate != null) {
|
} else if (update.groupV2MigrationSelfInvitedUpdate != null) {
|
||||||
describeGroupV2MigrationSelfInvitedUpdate(update.groupV2MigrationSelfInvitedUpdate, updates);
|
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) {
|
private void describeInviteLinkDisabledUpdate(@NonNull GroupInviteLinkDisabledUpdate update, @NonNull List<UpdateDescription> updates) {
|
||||||
boolean editorIsYou = selfIds.matches(update.updaterAci);
|
|
||||||
|
|
||||||
if (update.updaterAci == null) {
|
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));
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_off), R.drawable.ic_update_group_role_16));
|
||||||
} else {
|
} 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));
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_off_the_group_link), R.drawable.ic_update_group_role_16));
|
||||||
} else {
|
} else {
|
||||||
updates.add(updateDescription(R.string.MessageRecord_s_turned_off_the_group_link, update.updaterAci, R.drawable.ic_update_group_role_16));
|
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) {
|
private void describeInviteLinkEnabledUpdate(@NonNull GroupInviteLinkEnabledUpdate update, @NonNull List<UpdateDescription> updates) {
|
||||||
boolean editorIsYou = selfIds.matches(update.updaterAci);
|
|
||||||
|
|
||||||
if (update.updaterAci == null) {
|
if (update.updaterAci == null) {
|
||||||
if (update.linkRequiresAdminApproval) {
|
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));
|
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 {
|
} else {
|
||||||
if (editorIsYou) {
|
if (selfIds.matches(update.updaterAci)) {
|
||||||
if (update.linkRequiresAdminApproval) {
|
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));
|
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 {
|
} else {
|
||||||
|
@ -366,7 +405,7 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
private void describeGroupInvitationRevokedUpdate(@NonNull GroupInvitationRevokedUpdate update, @NonNull List<UpdateDescription> updates) {
|
private void describeGroupInvitationRevokedUpdate(@NonNull GroupInvitationRevokedUpdate update, @NonNull List<UpdateDescription> updates) {
|
||||||
int revokedMeCount = 0;
|
int revokedMeCount = 0;
|
||||||
for (GroupInvitationRevokedUpdate.Invitee invitee : update.invitees) {
|
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++;
|
revokedMeCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -409,17 +448,21 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
} else {
|
} else {
|
||||||
updates.add(updateDescription(R.string.MessageRecord_s_joined_the_group, update.newMemberAci, R.drawable.ic_update_group_add_16));
|
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 {
|
} else {
|
||||||
if (newMemberIsYou) {
|
if (newMemberIsYou) {
|
||||||
updates.add(0, updateDescription(R.string.MessageRecord_s_added_you, update.updaterAci, R.drawable.ic_update_group_add_16));
|
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 {
|
} else {
|
||||||
updates.add(updateDescription(R.string.MessageRecord_s_added_s, update.updaterAci, update.newMemberAci, R.drawable.ic_update_group_add_16));
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,7 +180,11 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||||
|
|
||||||
public @Nullable UpdateDescription getUpdateDisplayBody(@NonNull Context context, @Nullable Consumer<RecipientId> recipientClickHandler) {
|
public @Nullable UpdateDescription getUpdateDisplayBody(@NonNull Context context, @Nullable Consumer<RecipientId> recipientClickHandler) {
|
||||||
if (isGroupUpdate() && isGroupV2()) {
|
if (isGroupUpdate() && isGroupV2()) {
|
||||||
return getGv2ChangeDescription(context, getBody(), recipientClickHandler);
|
if (messageExtras != null) {
|
||||||
|
return getGv2ChangeDescription(context, messageExtras, recipientClickHandler);
|
||||||
|
} else {
|
||||||
|
return getGv2ChangeDescription(context, getBody(), recipientClickHandler);
|
||||||
|
}
|
||||||
} else if (isGroupUpdate() && isOutgoing()) {
|
} else if (isGroupUpdate() && isOutgoing()) {
|
||||||
return staticUpdateDescription(context.getString(R.string.MessageRecord_you_updated_group), R.drawable.ic_update_group_16);
|
return staticUpdateDescription(context.getString(R.string.MessageRecord_you_updated_group), R.drawable.ic_update_group_16);
|
||||||
} else if (isGroupUpdate()) {
|
} else if (isGroupUpdate()) {
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
import org.thoughtcrime.securesms.database.ThreadTable;
|
import org.thoughtcrime.securesms.database.ThreadTable;
|
||||||
import org.thoughtcrime.securesms.database.model.GroupRecord;
|
import org.thoughtcrime.securesms.database.model.GroupRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
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.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.groups.v2.GroupCandidateHelper;
|
import org.thoughtcrime.securesms.groups.v2.GroupCandidateHelper;
|
||||||
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
|
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
|
||||||
|
@ -1295,10 +1296,10 @@ final class GroupManagerV2 {
|
||||||
@Nullable GroupChange signedGroupChange,
|
@Nullable GroupChange signedGroupChange,
|
||||||
boolean sendToMembers)
|
boolean sendToMembers)
|
||||||
{
|
{
|
||||||
GroupId.V2 groupId = GroupId.v2(masterKey);
|
GroupId.V2 groupId = GroupId.v2(masterKey);
|
||||||
Recipient groupRecipient = Recipient.externalGroupExact(groupId);
|
Recipient groupRecipient = Recipient.externalGroupExact(groupId);
|
||||||
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, groupMutation, signedGroupChange);
|
GV2UpdateDescription updateDescription = GroupProtoUtil.createOutgoingGroupV2UpdateDescription(masterKey, groupMutation, signedGroupChange);
|
||||||
OutgoingMessage outgoingMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, decryptedGroupV2Context, System.currentTimeMillis());
|
OutgoingMessage outgoingMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, System.currentTimeMillis());
|
||||||
|
|
||||||
|
|
||||||
DecryptedGroupChange plainGroupChange = groupMutation.getGroupChange();
|
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.DecryptedGroupChange;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
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.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.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup;
|
import org.whispersystems.signalservice.api.groupsv2.PartialDecryptedGroup;
|
||||||
|
@ -45,6 +51,19 @@ public final class GroupProtoUtil {
|
||||||
throw new GroupNotAMemberException();
|
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,
|
public static DecryptedGroupV2Context createDecryptedGroupV2Context(@NonNull GroupMasterKey masterKey,
|
||||||
@NonNull GroupMutation groupMutation,
|
@NonNull GroupMutation groupMutation,
|
||||||
@Nullable GroupChange signedServerChange)
|
@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.DecryptedGroupChange;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
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.GroupTable;
|
||||||
import org.thoughtcrime.securesms.database.MessageTable;
|
import org.thoughtcrime.securesms.database.MessageTable;
|
||||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
import org.thoughtcrime.securesms.database.ThreadTable;
|
import org.thoughtcrime.securesms.database.ThreadTable;
|
||||||
import org.thoughtcrime.securesms.database.model.GroupRecord;
|
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.DecryptedGroupV2Context;
|
||||||
|
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.groups.GroupDoesNotExistException;
|
import org.thoughtcrime.securesms.groups.GroupDoesNotExistException;
|
||||||
import org.thoughtcrime.securesms.groups.GroupId;
|
import org.thoughtcrime.securesms.groups.GroupId;
|
||||||
|
@ -573,8 +576,8 @@ public class GroupsV2StateProcessor {
|
||||||
.deleteMembers(Collections.singletonList(serviceIds.getAci().toByteString()))
|
.deleteMembers(Collections.singletonList(serviceIds.getAci().toByteString()))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(decryptedGroup, simulatedGroupChange, simulatedGroupState), null);
|
GV2UpdateDescription updateDescription = GroupProtoUtil.createOutgoingGroupV2UpdateDescription(masterKey, new GroupMutation(decryptedGroup, simulatedGroupChange, simulatedGroupState), null);
|
||||||
OutgoingMessage leaveMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, decryptedGroupV2Context, System.currentTimeMillis());
|
OutgoingMessage leaveMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, System.currentTimeMillis());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MessageTable mmsDatabase = SignalDatabase.messages();
|
MessageTable mmsDatabase = SignalDatabase.messages();
|
||||||
|
@ -803,13 +806,18 @@ public class GroupsV2StateProcessor {
|
||||||
|
|
||||||
boolean outgoing = !editor.isPresent() || aci.equals(editor.get());
|
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) {
|
if (outgoing) {
|
||||||
try {
|
try {
|
||||||
MessageTable mmsDatabase = SignalDatabase.messages();
|
MessageTable mmsDatabase = SignalDatabase.messages();
|
||||||
ThreadTable threadTable = SignalDatabase.threads();
|
ThreadTable threadTable = SignalDatabase.threads();
|
||||||
RecipientId recipientId = recipientTable.getOrInsertFromGroupId(groupId);
|
RecipientId recipientId = recipientTable.getOrInsertFromGroupId(groupId);
|
||||||
Recipient recipient = Recipient.resolved(recipientId);
|
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 threadId = threadTable.getOrCreateThreadIdFor(recipient);
|
||||||
long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null);
|
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.StoryType
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
|
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.GiftBadge
|
||||||
|
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
|
||||||
import org.thoughtcrime.securesms.groups.GroupId
|
import org.thoughtcrime.securesms.groups.GroupId
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
|
@ -36,7 +38,8 @@ class IncomingMessage(
|
||||||
sharedContacts: List<Contact> = emptyList(),
|
sharedContacts: List<Contact> = emptyList(),
|
||||||
linkPreviews: List<LinkPreview> = emptyList(),
|
linkPreviews: List<LinkPreview> = emptyList(),
|
||||||
mentions: List<Mention> = emptyList(),
|
mentions: List<Mention> = emptyList(),
|
||||||
val giftBadge: GiftBadge? = null
|
val giftBadge: GiftBadge? = null,
|
||||||
|
val messageExtras: MessageExtras? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val attachments: List<Attachment> = ArrayList(attachments)
|
val attachments: List<Attachment> = ArrayList(attachments)
|
||||||
|
@ -104,9 +107,8 @@ class IncomingMessage(
|
||||||
serverTimeMillis = timestamp,
|
serverTimeMillis = timestamp,
|
||||||
groupId = groupId,
|
groupId = groupId,
|
||||||
groupContext = messageGroupContext,
|
groupContext = messageGroupContext,
|
||||||
serverGuid = serverGuid,
|
type = MessageType.GROUP_UPDATE,
|
||||||
body = messageGroupContext.encodedGroupContext,
|
messageExtras = MessageExtras(gv2UpdateDescription = GV2UpdateDescription(gv2ChangeDescription = groupContext, groupChangeUpdate = null))
|
||||||
type = MessageType.GROUP_UPDATE
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.DecryptedGroupChange;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
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.messages.SignalServiceProtoUtil;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
@ -30,7 +31,6 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public final class MessageGroupContext {
|
public final class MessageGroupContext {
|
||||||
|
|
||||||
@NonNull private final String encodedGroupContext;
|
|
||||||
@NonNull private final GroupProperties group;
|
@NonNull private final GroupProperties group;
|
||||||
@Nullable private final GroupV1Properties groupV1;
|
@Nullable private final GroupV1Properties groupV1;
|
||||||
@Nullable private final GroupV2Properties groupV2;
|
@Nullable private final GroupV2Properties groupV2;
|
||||||
|
@ -38,7 +38,6 @@ public final class MessageGroupContext {
|
||||||
public MessageGroupContext(@NonNull String encodedGroupContext, boolean v2)
|
public MessageGroupContext(@NonNull String encodedGroupContext, boolean v2)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
this.encodedGroupContext = encodedGroupContext;
|
|
||||||
if (v2) {
|
if (v2) {
|
||||||
this.groupV1 = null;
|
this.groupV1 = null;
|
||||||
this.groupV2 = new GroupV2Properties(DecryptedGroupV2Context.ADAPTER.decode(Base64.decode(encodedGroupContext)));
|
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) {
|
public MessageGroupContext(@NonNull GroupContext group) {
|
||||||
this.encodedGroupContext = Base64.encodeWithPadding(group.encode());
|
|
||||||
this.groupV1 = new GroupV1Properties(group);
|
this.groupV1 = new GroupV1Properties(group);
|
||||||
this.groupV2 = null;
|
this.groupV2 = null;
|
||||||
this.group = groupV1;
|
this.group = groupV1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageGroupContext(@NonNull DecryptedGroupV2Context group) {
|
public MessageGroupContext(@NonNull DecryptedGroupV2Context group) {
|
||||||
this.encodedGroupContext = Base64.encodeWithPadding(group.encode());
|
|
||||||
this.groupV1 = null;
|
this.groupV1 = null;
|
||||||
this.groupV2 = new GroupV2Properties(group);
|
this.groupV2 = new GroupV2Properties(group);
|
||||||
this.group = groupV2;
|
this.group = groupV2;
|
||||||
|
@ -82,10 +91,6 @@ public final class MessageGroupContext {
|
||||||
return groupV2 != null;
|
return groupV2 != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull String getEncodedGroupContext() {
|
|
||||||
return encodedGroupContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return group.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.ParentStoryId
|
||||||
import org.thoughtcrime.securesms.database.model.StoryType
|
import org.thoughtcrime.securesms.database.model.StoryType
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
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.GiftBadge
|
||||||
|
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.sms.GroupV2UpdateMessageUtil
|
import org.thoughtcrime.securesms.sms.GroupV2UpdateMessageUtil
|
||||||
|
@ -52,7 +53,8 @@ data class OutgoingMessage(
|
||||||
val scheduledDate: Long = -1,
|
val scheduledDate: Long = -1,
|
||||||
val messageToEdit: Long = 0,
|
val messageToEdit: Long = 0,
|
||||||
val isReportSpam: Boolean = false,
|
val isReportSpam: Boolean = false,
|
||||||
val isMessageRequestAccept: Boolean = false
|
val isMessageRequestAccept: Boolean = false,
|
||||||
|
val messageExtras: MessageExtras? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val isV2Group: Boolean = messageGroupContext != null && GroupV2UpdateMessageUtil.isGroupV2(messageGroupContext)
|
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.
|
* Helper for creating a group update message when a state change occurs and needs to be sent to others.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun groupUpdateMessage(threadRecipient: Recipient, group: DecryptedGroupV2Context, sentTimeMillis: Long): OutgoingMessage {
|
fun groupUpdateMessage(threadRecipient: Recipient, update: GV2UpdateDescription, sentTimeMillis: Long): OutgoingMessage {
|
||||||
val groupContext = MessageGroupContext(group)
|
val messageExtras = MessageExtras(gv2UpdateDescription = update)
|
||||||
|
val groupContext = MessageGroupContext(update.gv2ChangeDescription!!)
|
||||||
|
|
||||||
return OutgoingMessage(
|
return OutgoingMessage(
|
||||||
threadRecipient = threadRecipient,
|
threadRecipient = threadRecipient,
|
||||||
body = groupContext.encodedGroupContext,
|
|
||||||
sentTimeMillis = sentTimeMillis,
|
sentTimeMillis = sentTimeMillis,
|
||||||
messageGroupContext = groupContext,
|
messageGroupContext = groupContext,
|
||||||
isGroup = true,
|
isGroup = true,
|
||||||
isGroupUpdate = true,
|
isGroupUpdate = true,
|
||||||
isSecure = true
|
isSecure = true,
|
||||||
|
messageExtras = messageExtras
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +263,6 @@ data class OutgoingMessage(
|
||||||
): OutgoingMessage {
|
): OutgoingMessage {
|
||||||
return OutgoingMessage(
|
return OutgoingMessage(
|
||||||
threadRecipient = threadRecipient,
|
threadRecipient = threadRecipient,
|
||||||
body = groupContext.encodedGroupContext,
|
|
||||||
isGroup = true,
|
isGroup = true,
|
||||||
isGroupUpdate = true,
|
isGroupUpdate = true,
|
||||||
messageGroupContext = groupContext,
|
messageGroupContext = groupContext,
|
||||||
|
|
|
@ -52,7 +52,7 @@ message AccountData {
|
||||||
bool linkPreviews = 5;
|
bool linkPreviews = 5;
|
||||||
bool notDiscoverableByPhoneNumber = 6;
|
bool notDiscoverableByPhoneNumber = 6;
|
||||||
bool preferContactAvatars = 7;
|
bool preferContactAvatars = 7;
|
||||||
uint32 universalExpireTimer = 8;
|
uint32 universalExpireTimer = 8; // 0 means no universal expire timer.
|
||||||
repeated string preferredReactionEmoji = 9;
|
repeated string preferredReactionEmoji = 9;
|
||||||
bool displayBadgesOnProfile = 10;
|
bool displayBadgesOnProfile = 10;
|
||||||
bool keepMutedChatsArchived = 11;
|
bool keepMutedChatsArchived = 11;
|
||||||
|
@ -132,7 +132,7 @@ message Chat {
|
||||||
uint64 recipientId = 2;
|
uint64 recipientId = 2;
|
||||||
bool archived = 3;
|
bool archived = 3;
|
||||||
uint32 pinnedOrder = 4; // 0 = unpinned, otherwise chat is considered pinned and will be displayed in ascending order
|
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;
|
uint64 muteUntilMs = 6;
|
||||||
bool markedUnread = 7;
|
bool markedUnread = 7;
|
||||||
bool dontNotifyForMentionsIfMuted = 8;
|
bool dontNotifyForMentionsIfMuted = 8;
|
||||||
|
@ -537,8 +537,10 @@ message SimpleChatUpdate {
|
||||||
Type type = 1;
|
Type type = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For 1:1 chat updates only.
|
||||||
|
// For group thread updates use GroupExpirationTimerUpdate.
|
||||||
message ExpirationTimerChatUpdate {
|
message ExpirationTimerChatUpdate {
|
||||||
uint32 expiresInMs = 1;
|
uint32 expiresInMs = 1; // 0 means the expiration timer was disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
message ProfileChangeChatUpdate {
|
message ProfileChangeChatUpdate {
|
||||||
|
@ -591,6 +593,7 @@ message GroupChangeChatUpdate {
|
||||||
GroupV2MigrationInvitedMembersUpdate groupV2MigrationInvitedMembersUpdate = 31;
|
GroupV2MigrationInvitedMembersUpdate groupV2MigrationInvitedMembersUpdate = 31;
|
||||||
GroupV2MigrationDroppedMembersUpdate groupV2MigrationDroppedMembersUpdate = 32;
|
GroupV2MigrationDroppedMembersUpdate groupV2MigrationDroppedMembersUpdate = 32;
|
||||||
GroupSequenceOfRequestsAndCancelsUpdate groupSequenceOfRequestsAndCancelsUpdate = 33;
|
GroupSequenceOfRequestsAndCancelsUpdate groupSequenceOfRequestsAndCancelsUpdate = 33;
|
||||||
|
GroupExpirationTimerUpdate groupExpirationTimerUpdate = 34;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -794,6 +797,12 @@ message GroupV2MigrationDroppedMembersUpdate {
|
||||||
int32 droppedMembersCount = 1;
|
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 {
|
message StickerPack {
|
||||||
bytes id = 1;
|
bytes id = 1;
|
||||||
bytes key = 2;
|
bytes key = 2;
|
||||||
|
|
|
@ -376,7 +376,10 @@ message ExternalLaunchTransactionState {
|
||||||
}
|
}
|
||||||
|
|
||||||
message MessageExtras {
|
message MessageExtras {
|
||||||
GV2UpdateDescription gv2UpdateDescription = 1;
|
oneof extra {
|
||||||
|
GV2UpdateDescription gv2UpdateDescription = 1;
|
||||||
|
signalservice.GroupContext gv1Context = 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message GV2UpdateDescription {
|
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.DecryptedGroupChange;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
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.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
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.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -62,6 +65,8 @@ public final class GroupsV2UpdateMessageProducerTest {
|
||||||
private ACI alice;
|
private ACI alice;
|
||||||
private ACI bob;
|
private ACI bob;
|
||||||
|
|
||||||
|
private ServiceIds selfIds;
|
||||||
|
|
||||||
private GroupsV2UpdateMessageProducer producer;
|
private GroupsV2UpdateMessageProducer producer;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
|
@ -79,6 +84,8 @@ public final class GroupsV2UpdateMessageProducerTest {
|
||||||
alice = ACI.from(UUID.randomUUID());
|
alice = ACI.from(UUID.randomUUID());
|
||||||
bob = ACI.from(UUID.randomUUID());
|
bob = ACI.from(UUID.randomUUID());
|
||||||
|
|
||||||
|
selfIds = new ServiceIds(you, PNI.from(UUID.randomUUID()));
|
||||||
|
|
||||||
recipientIdMockedStatic.when(() -> RecipientId.from(anyLong())).thenCallRealMethod();
|
recipientIdMockedStatic.when(() -> RecipientId.from(anyLong())).thenCallRealMethod();
|
||||||
|
|
||||||
RecipientId aliceId = RecipientId.from(1);
|
RecipientId aliceId = RecipientId.from(1);
|
||||||
|
@ -87,7 +94,7 @@ public final class GroupsV2UpdateMessageProducerTest {
|
||||||
Recipient aliceRecipient = recipientWithName(aliceId, "Alice");
|
Recipient aliceRecipient = recipientWithName(aliceId, "Alice");
|
||||||
Recipient bobRecipient = recipientWithName(bobId, "Bob");
|
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(alice)).thenReturn(aliceId);
|
||||||
recipientIdMockedStatic.when(() -> RecipientId.from(bob)).thenReturn(bobId);
|
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());
|
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) {
|
private @NonNull List<String> describeChange(@NonNull DecryptedGroupChange change) {
|
||||||
return describeChange(null, change);
|
return describeChange(null, change);
|
||||||
}
|
}
|
||||||
|
@ -1429,10 +1457,20 @@ public final class GroupsV2UpdateMessageProducerTest {
|
||||||
private @NonNull List<String> describeChange(@Nullable DecryptedGroup previousGroupState,
|
private @NonNull List<String> describeChange(@Nullable DecryptedGroup previousGroupState,
|
||||||
@NonNull DecryptedGroupChange change)
|
@NonNull DecryptedGroupChange change)
|
||||||
{
|
{
|
||||||
return Stream.of(producer.describeChanges(previousGroupState, change))
|
List<String> convertedChange = describeConvertedChange(previousGroupState, change);
|
||||||
.map(UpdateDescription::getSpannable)
|
List<String> describedChange = Stream.of(producer.describeChanges(previousGroupState, change))
|
||||||
.map(Spannable::toString)
|
.map(UpdateDescription::getSpannable)
|
||||||
.toList();
|
.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) {
|
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) {
|
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) {
|
private static GroupStateBuilder newGroupBy(ACI foundingMember, int revision) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue