Convert gv2 update messages to backup distinct protos.

This commit is contained in:
Clark 2024-01-24 15:53:52 -05:00 committed by Nicholas Tinsley
parent 0036b8e2d6
commit 98865d61dd
15 changed files with 1019 additions and 81 deletions

View file

@ -118,7 +118,8 @@ class ConversationElementGenerator {
null,
null,
0,
false
false,
null
)
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(

View file

@ -84,7 +84,12 @@ abstract class ConversationListDataSource implements PagedDataSource<Long, Conve
if (!MessageTypes.isGroupV2(record.getType())) {
needsResolve.add(record.getRecipient().getId());
} else if (MessageTypes.isGroupUpdate(record.getType())) {
UpdateDescription description = MessageRecord.getGv2ChangeDescription(ApplicationDependencies.getApplication(), record.getBody(), null);
UpdateDescription description;
if (record.getMessageExtras() != null) {
description = MessageRecord.getGv2ChangeDescription(ApplicationDependencies.getApplication(), record.getMessageExtras(), null);
} else {
description = MessageRecord.getGv2ChangeDescription(ApplicationDependencies.getApplication(), record.getBody(), null);
}
needsResolve.addAll(description.getMentioned().stream().map(RecipientId::from).collect(Collectors.toList()));
}
}

View file

@ -572,7 +572,11 @@ public final class ConversationListItem extends ConstraintLayout implements Bind
}
} else if (MessageTypes.isGroupUpdate(thread.getType())) {
if (thread.getRecipient().isPushV2Group()) {
return emphasisAdded(context, MessageRecord.getGv2ChangeDescription(context, thread.getBody(), null), defaultTint);
if (thread.getMessageExtras() != null) {
return emphasisAdded(context, MessageRecord.getGv2ChangeDescription(context, thread.getMessageExtras(), null), defaultTint);
} else {
return emphasisAdded(context, MessageRecord.getGv2ChangeDescription(context, thread.getBody(), null), defaultTint);
}
} else {
return emphasisAdded(context, context.getString(R.string.ThreadRecord_group_updated), R.drawable.ic_update_group_16, defaultTint);
}

View file

@ -108,6 +108,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExportState
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
@ -207,6 +208,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
const val LATEST_REVISION_ID = "latest_revision_id"
const val ORIGINAL_MESSAGE_ID = "original_message_id"
const val REVISION_NUMBER = "revision_number"
const val MESSAGE_EXTRAS = "message_extras"
const val QUOTE_NOT_PRESENT_ID = 0L
const val QUOTE_TARGET_MISSING_ID = -1L
@ -264,7 +266,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
$SCHEDULED_DATE INTEGER DEFAULT -1,
$LATEST_REVISION_ID INTEGER DEFAULT NULL REFERENCES $TABLE_NAME ($ID) ON DELETE CASCADE,
$ORIGINAL_MESSAGE_ID INTEGER DEFAULT NULL REFERENCES $TABLE_NAME ($ID) ON DELETE CASCADE,
$REVISION_NUMBER INTEGER DEFAULT 0
$REVISION_NUMBER INTEGER DEFAULT 0,
$MESSAGE_EXTRAS BLOB DEFAULT NULL
)
"""
@ -343,7 +346,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
SCHEDULED_DATE,
LATEST_REVISION_ID,
ORIGINAL_MESSAGE_ID,
REVISION_NUMBER
REVISION_NUMBER,
MESSAGE_EXTRAS
)
private val MMS_PROJECTION: Array<String> = MMS_PROJECTION_BASE + "NULL AS ${AttachmentTable.ATTACHMENT_JSON_ALIAS}"
@ -4985,6 +4989,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val originalMessageId: MessageId? = cursor.requireLong(ORIGINAL_MESSAGE_ID).let { if (it == 0L) null else MessageId(it) }
val editCount = cursor.requireInt(REVISION_NUMBER)
val isRead = cursor.requireBoolean(READ)
val messageExtraBytes = cursor.requireBlob(MESSAGE_EXTRAS)
val messageExtras = if (messageExtraBytes != null) MessageExtras.ADAPTER.decode(messageExtraBytes) else null
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
hasReadReceipt = false
@ -5072,7 +5078,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
latestRevisionId,
originalMessageId,
editCount,
isRead
isRead,
messageExtras
)
}

View file

@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
import org.thoughtcrime.securesms.database.model.serialize
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.BadGroupIdException
@ -97,6 +98,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
const val SNIPPET_URI = "snippet_uri"
const val SNIPPET_CONTENT_TYPE = "snippet_content_type"
const val SNIPPET_EXTRAS = "snippet_extras"
const val SNIPPET_MESSAGE_EXTRAS = "snippet_message_extras"
const val ARCHIVED = "archived"
const val STATUS = "status"
const val HAS_DELIVERY_RECEIPT = "has_delivery_receipt"
@ -137,7 +139,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
$LAST_SCROLLED INTEGER DEFAULT 0,
$PINNED INTEGER DEFAULT 0,
$UNREAD_SELF_MENTION_COUNT INTEGER DEFAULT 0,
$ACTIVE INTEGER DEFAULT 0
$ACTIVE INTEGER DEFAULT 0,
$SNIPPET_MESSAGE_EXTRAS BLOB DEFAULT NULL
)
"""
@ -164,6 +167,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
SNIPPET_URI,
SNIPPET_CONTENT_TYPE,
SNIPPET_EXTRAS,
SNIPPET_MESSAGE_EXTRAS,
ARCHIVED,
STATUS,
HAS_DELIVERY_RECEIPT,
@ -223,7 +227,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
expiresIn: Long,
readReceiptCount: Int,
unreadCount: Int,
unreadMentionCount: Int
unreadMentionCount: Int,
messageExtras: MessageExtras?
) {
var extraSerialized: String? = null
@ -249,7 +254,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
EXPIRES_IN to expiresIn,
ACTIVE to 1,
UNREAD_COUNT to unreadCount,
UNREAD_SELF_MENTION_COUNT to unreadMentionCount
UNREAD_SELF_MENTION_COUNT to unreadMentionCount,
SNIPPET_MESSAGE_EXTRAS to messageExtras?.encode()
)
writableDatabase
@ -1479,7 +1485,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
expiresIn = 0,
readReceiptCount = 0,
unreadCount = 0,
unreadMentionCount = 0
unreadMentionCount = 0,
messageExtras = null
)
}
return@withinTransaction true
@ -1508,7 +1515,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
expiresIn = record.expiresIn,
readReceiptCount = record.hasReadReceipt().toInt(),
unreadCount = unreadCount,
unreadMentionCount = unreadMentionCount
unreadMentionCount = unreadMentionCount,
messageExtras = record.messageExtras
)
if (notifyListeners) {
@ -1667,6 +1675,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
SNIPPET_URI to null,
SNIPPET_CONTENT_TYPE to null,
SNIPPET_EXTRAS to null,
SNIPPET_MESSAGE_EXTRAS to null,
UNREAD_COUNT to 0,
ARCHIVED to 0,
STATUS to 0,
@ -1898,6 +1907,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
val hasReadReceipt = TextSecurePreferences.isReadReceiptsEnabled(context) && cursor.requireBoolean(HAS_READ_RECEIPT)
val extraString = cursor.getString(cursor.getColumnIndexOrThrow(SNIPPET_EXTRAS))
val messageExtras = cursor.getBlob(cursor.getColumnIndexOrThrow(SNIPPET_MESSAGE_EXTRAS))
val extra: Extra? = if (extraString != null) {
try {
val jsonObject = SaneJSONObject(JSONObject(extraString))

View file

@ -74,6 +74,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V213_FixUsernameInE
import org.thoughtcrime.securesms.database.helpers.migration.V214_PhoneNumberSharingColumn
import org.thoughtcrime.securesms.database.helpers.migration.V215_RemoveAttachmentUniqueId
import org.thoughtcrime.securesms.database.helpers.migration.V216_PhoneNumberDiscoverable
import org.thoughtcrime.securesms.database.helpers.migration.V217_MessageTableExtrasColumn
/**
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
@ -150,10 +151,11 @@ object SignalDatabaseMigrations {
213 to V213_FixUsernameInE164Column,
214 to V214_PhoneNumberSharingColumn,
215 to V215_RemoveAttachmentUniqueId,
216 to V216_PhoneNumberDiscoverable
216 to V216_PhoneNumberDiscoverable,
217 to V217_MessageTableExtrasColumn
)
const val DATABASE_VERSION = 216
const val DATABASE_VERSION = 217
@JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import net.zetetic.database.sqlcipher.SQLiteDatabase
/**
* Adds a message_extras column to the messages table. This allows us to
* store extra data for messages in a more future proof and structured way.
*/
@Suppress("ClassName")
object V217_MessageTableExtrasColumn : SignalDatabaseMigration {
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("ALTER TABLE message ADD COLUMN message_extras BLOB DEFAULT NULL")
db.execSQL("ALTER TABLE thread ADD COLUMN snippet_message_extras BLOB DEFAULT NULL")
}
}

View file

@ -25,17 +25,53 @@ import org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemov
import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember;
import org.signal.storageservice.protos.groups.local.EnabledState;
import org.thoughtcrime.securesms.R;
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.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.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.GroupUnknownInviteeUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2AccessLevel;
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationDroppedMembersUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationInvitedMembersUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationSelfInvitedUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedOtherUserToGroupUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedToGroupUpdate;
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription;
import org.thoughtcrime.securesms.groups.GV2AccessLevelUtil;
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.SpanUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceIds;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@ -104,6 +140,485 @@ final class GroupsV2UpdateMessageProducer {
}
}
List<UpdateDescription> describeChanges(@NonNull List<GroupChangeChatUpdate.Update> groupUpdates) {
List<UpdateDescription> updates = new LinkedList<>();
for (GroupChangeChatUpdate.Update update : groupUpdates) {
describeUpdate(update, updates);
}
return updates;
}
private void describeUpdate(@NonNull GroupChangeChatUpdate.Update update, @NonNull List<UpdateDescription> updates) {
if (update.genericGroupUpdate != null) {
describeGenericGroupUpdate(update.genericGroupUpdate, updates);
} else if (update.groupCreationUpdate != null) {
describeGroupCreationUpdate(update.groupCreationUpdate, updates);
} else if (update.groupNameUpdate != null) {
describeGroupNameUpdate(update.groupNameUpdate, updates);
} else if (update.groupAvatarUpdate != null) {
describeAvatarChange(update.groupAvatarUpdate, updates);
} else if (update.groupDescriptionUpdate != null) {
describeDescriptionChange(update.groupDescriptionUpdate, updates);
} else if (update.groupMembershipAccessLevelChangeUpdate != null) {
describeGroupMembershipAccessLevelChange(update.groupMembershipAccessLevelChangeUpdate, updates);
} else if (update.groupAttributesAccessLevelChangeUpdate != null) {
describeGroupAttributesAccessLevelChange(update.groupAttributesAccessLevelChangeUpdate, updates);
} else if (update.groupAnnouncementOnlyChangeUpdate != null) {
describeGroupAnnouncementOnlyUpdate(update.groupAnnouncementOnlyChangeUpdate, updates);
} else if (update.groupAdminStatusUpdate != null) {
describeAdminStatusChange(update.groupAdminStatusUpdate, updates);
} else if (update.groupMemberLeftUpdate != null) {
describeGroupMemberLeftChange(update.groupMemberLeftUpdate, updates);
} else if (update.groupMemberRemovedUpdate != null) {
describeGroupMemberRemovedChange(update.groupMemberRemovedUpdate, updates);
} else if (update.selfInvitedToGroupUpdate != null) {
describeSelfInvitedToGroupUpdate(update.selfInvitedToGroupUpdate, updates);
} else if (update.selfInvitedOtherUserToGroupUpdate != null) {
describeSelfInvitedOtherUserToGroupUpdate(update.selfInvitedOtherUserToGroupUpdate, updates);
} else if (update.groupUnknownInviteeUpdate != null) {
describeUnknownUsersInvitedUpdate(update.groupUnknownInviteeUpdate, updates);
} else if (update.groupInvitationAcceptedUpdate != null) {
describeGroupInvitationAcceptedUpdate(update.groupInvitationAcceptedUpdate, updates);
} else if (update.groupMemberJoinedUpdate != null) {
describeGroupMemberJoinedUpdate(update.groupMemberJoinedUpdate, updates);
} else if (update.groupMemberAddedUpdate != null) {
describeGroupMemberAddedUpdate(update.groupMemberAddedUpdate, updates);
} else if (update.groupInvitationDeclinedUpdate != null) {
describeGroupInvitationDeclinedUpdate(update.groupInvitationDeclinedUpdate, updates);
} else if (update.groupInvitationRevokedUpdate != null) {
describeGroupInvitationRevokedUpdate(update.groupInvitationRevokedUpdate, updates);
} else if (update.groupJoinRequestUpdate != null) {
describeGroupJoinRequestUpdate(update.groupJoinRequestUpdate, updates);
} else if (update.groupJoinRequestApprovalUpdate != null) {
describeGroupJoinRequestApprovedUpdate(update.groupJoinRequestApprovalUpdate, updates);
} else if (update.groupJoinRequestCanceledUpdate != null) {
describeGroupJoinRequestCanceledUpdate(update.groupJoinRequestCanceledUpdate, updates);
} else if (update.groupInviteLinkResetUpdate != null) {
describeInviteLinkResetUpdate(update.groupInviteLinkResetUpdate, updates);
} else if (update.groupInviteLinkEnabledUpdate != null) {
describeInviteLinkEnabledUpdate(update.groupInviteLinkEnabledUpdate, updates);
} else if (update.groupInviteLinkDisabledUpdate != null) {
describeInviteLinkDisabledUpdate(update.groupInviteLinkDisabledUpdate, updates);
} else if (update.groupInviteLinkAdminApprovalUpdate != null) {
describeGroupInviteLinkAdminApprovalUpdate(update.groupInviteLinkAdminApprovalUpdate, updates);
} else if (update.groupV2MigrationUpdate != null) {
describeGroupV2MigrationUpdate(update.groupV2MigrationUpdate, updates);
} else if (update.groupV2MigrationDroppedMembersUpdate != null) {
describeGroupV2MigrationDroppedMembersUpdate(update.groupV2MigrationDroppedMembersUpdate, updates);
} else if (update.groupV2MigrationInvitedMembersUpdate != null) {
describeGroupV2MigrationInvitedMembersUpdate(update.groupV2MigrationInvitedMembersUpdate, updates);
} else if (update.groupV2MigrationSelfInvitedUpdate != null) {
describeGroupV2MigrationSelfInvitedUpdate(update.groupV2MigrationSelfInvitedUpdate, updates);
}
}
private void describeGroupV2MigrationSelfInvitedUpdate(@NonNull GroupV2MigrationSelfInvitedUpdate update, @NonNull List<UpdateDescription> updates) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_couldnt_be_added_to_the_new_group_and_have_been_invited_to_join), R.drawable.ic_update_group_add_16));
}
private void describeGroupV2MigrationDroppedMembersUpdate(@NonNull GroupV2MigrationDroppedMembersUpdate update, @NonNull List<UpdateDescription> updates) {
updates.add(updateDescription(context.getResources().getQuantityString(R.plurals.MessageRecord_members_couldnt_be_added_to_the_new_group_and_have_been_removed, update.droppedMembersCount, update.droppedMembersCount), R.drawable.ic_update_group_remove_16));
}
private void describeGroupV2MigrationInvitedMembersUpdate(@NonNull GroupV2MigrationInvitedMembersUpdate update, @NonNull List<UpdateDescription> updates) {
updates.add(updateDescription(context.getResources().getQuantityString(R.plurals.MessageRecord_members_couldnt_be_added_to_the_new_group_and_have_been_invited, update.invitedMembersCount, update.invitedMembersCount), R.drawable.ic_update_group_remove_16));
}
private void describeGroupV2MigrationUpdate(@NonNull GroupV2MigrationUpdate update, @NonNull List<UpdateDescription> updates) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_this_group_was_updated_to_a_new_group), R.drawable.ic_update_group_role_16));
}
private void describeGroupInviteLinkAdminApprovalUpdate(@NonNull GroupInviteLinkAdminApprovalUpdate update, @NonNull List<UpdateDescription> updates) {
if (update.updaterAci == null) {
if (update.linkRequiresAdminApproval) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_on), R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_off), R.drawable.ic_update_group_role_16));
}
} else {
if (selfIds.matches(update.updaterAci)) {
if (update.linkRequiresAdminApproval) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_admin_approval_for_the_group_link), R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_off_admin_approval_for_the_group_link), R.drawable.ic_update_group_role_16));
}
} else {
if (update.linkRequiresAdminApproval) {
updates.add(updateDescription(R.string.MessageRecord_s_turned_on_admin_approval_for_the_group_link, update.updaterAci, R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_turned_off_admin_approval_for_the_group_link, update.updaterAci, R.drawable.ic_update_group_role_16));
}
}
}
}
private void describeInviteLinkDisabledUpdate(@NonNull GroupInviteLinkDisabledUpdate update, @NonNull List<UpdateDescription> updates) {
boolean editorIsYou = selfIds.matches(update.updaterAci);
if (update.updaterAci == null) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_off), R.drawable.ic_update_group_role_16));
} else {
if (editorIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_off_the_group_link), R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_turned_off_the_group_link, update.updaterAci, R.drawable.ic_update_group_role_16));
}
}
}
private void describeInviteLinkEnabledUpdate(@NonNull GroupInviteLinkEnabledUpdate update, @NonNull List<UpdateDescription> updates) {
boolean editorIsYou = selfIds.matches(update.updaterAci);
if (update.updaterAci == null) {
if (update.linkRequiresAdminApproval) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_on), R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_off), R.drawable.ic_update_group_role_16));
}
} else {
if (editorIsYou) {
if (update.linkRequiresAdminApproval) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_on), R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_off), R.drawable.ic_update_group_role_16));
}
} else {
if (update.linkRequiresAdminApproval) {
updates.add(updateDescription(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_on, update.updaterAci, R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_off, update.updaterAci, R.drawable.ic_update_group_role_16));
}
}
}
}
private void describeInviteLinkResetUpdate(@NonNull GroupInviteLinkResetUpdate update, @NonNull List<UpdateDescription> updates) {
if (update.updaterAci == null) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_reset), R.drawable.ic_update_group_role_16));
} else {
if (selfIds.matches(update.updaterAci)) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_reset_the_group_link), R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_reset_the_group_link, update.updaterAci, R.drawable.ic_update_group_role_16));
}
}
}
private void describeGroupJoinRequestCanceledUpdate(@NonNull GroupJoinRequestCanceledUpdate update, @NonNull List<UpdateDescription> updates) {
boolean requestingMemberIsYou = selfIds.matches(update.requestorAci);
if (requestingMemberIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_canceled_your_request_to_join_the_group), R.drawable.ic_update_group_decline_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_canceled_their_request_to_join_the_group, update.requestorAci, R.drawable.ic_update_group_decline_16));
}
}
private void describeGroupJoinRequestApprovedUpdate(@NonNull GroupJoinRequestApprovalUpdate update, @NonNull List<UpdateDescription> updates) {
boolean requestingMemberIsYou = selfIds.matches(update.requestorAci);
if (update.wasApproved) {
if (update.updaterAci == null) {
if (requestingMemberIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_your_request_to_join_the_group_has_been_approved), R.drawable.ic_update_group_accept_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_a_request_to_join_the_group_from_s_has_been_approved, update.requestorAci, R.drawable.ic_update_group_accept_16));
}
} else {
if (requestingMemberIsYou) {
updates.add(updateDescription(R.string.MessageRecord_s_approved_your_request_to_join_the_group, update.updaterAci, R.drawable.ic_update_group_accept_16));
} else {
boolean editorIsYou = selfIds.matches(update.updaterAci);
if (editorIsYou) {
updates.add(updateDescription(R.string.MessageRecord_you_approved_a_request_to_join_the_group_from_s, update.requestorAci, R.drawable.ic_update_group_accept_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_approved_a_request_to_join_the_group_from_s, update.updaterAci, update.requestorAci, R.drawable.ic_update_group_accept_16));
}
}
}
} else {
if (update.updaterAci == null) {
if (requestingMemberIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_your_request_to_join_the_group_has_been_denied_by_an_admin), R.drawable.ic_update_group_decline_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_a_request_to_join_the_group_from_s_has_been_denied, update.requestorAci, R.drawable.ic_update_group_decline_16));
}
} else {
if (requestingMemberIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_your_request_to_join_the_group_has_been_denied_by_an_admin), R.drawable.ic_update_group_decline_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_denied_a_request_to_join_the_group_from_s, update.updaterAci, update.requestorAci, R.drawable.ic_update_group_decline_16));
}
}
}
}
private void describeGroupJoinRequestUpdate(@NonNull GroupJoinRequestUpdate update, @NonNull List<UpdateDescription> updates) {
if (selfIds.matches(update.requestorAci)) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_sent_a_request_to_join_the_group), R.drawable.ic_update_group_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_requested_to_join_via_the_group_link, update.requestorAci, R.drawable.ic_update_group_16));
}
}
private void describeGroupInvitationRevokedUpdate(@NonNull GroupInvitationRevokedUpdate update, @NonNull List<UpdateDescription> updates) {
int revokedMeCount = 0;
for (GroupInvitationRevokedUpdate.Invitee invitee : update.invitees) {
if (selfIds.matches(invitee.inviteeAci) || selfIds.matches(invitee.inviteePni)) {
revokedMeCount++;
}
}
int notMeInvitees = update.invitees.size() - revokedMeCount;
if (update.updaterAci == null) {
if (revokedMeCount > 0) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_an_admin_revoked_your_invitation_to_the_group), R.drawable.ic_update_group_decline_16));
}
if (notMeInvitees > 0) {
updates.add(updateDescription(context.getResources().getQuantityString(R.plurals.MessageRecord_d_invitations_were_revoked, notMeInvitees, notMeInvitees), R.drawable.ic_update_group_decline_16));
}
} else {
if (revokedMeCount > 0) {
updates.add(updateDescription(R.string.MessageRecord_s_revoked_your_invitation_to_the_group, update.updaterAci, R.drawable.ic_update_group_decline_16));
}
if (selfIds.matches(update.updaterAci)) {
updates.add(updateDescription(context.getResources().getQuantityString(R.plurals.MessageRecord_you_revoked_invites, notMeInvitees, notMeInvitees), R.drawable.ic_update_group_decline_16));
} else {
updates.add(updateDescription(R.plurals.MessageRecord_s_revoked_invites, notMeInvitees, update.updaterAci, notMeInvitees, R.drawable.ic_update_group_decline_16));
}
}
}
private void describeGroupInvitationDeclinedUpdate(@NonNull GroupInvitationDeclinedUpdate update, @NonNull List<UpdateDescription> updates) {
if (selfIds.matches(update.inviteeAci)) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_declined_the_invitation_to_the_group), R.drawable.ic_update_group_decline_16));
} else {
updates.add(updateDescription(context.getString(R.string.MessageRecord_someone_declined_an_invitation_to_the_group), R.drawable.ic_update_group_decline_16));
}
}
private void describeGroupMemberAddedUpdate(@NonNull GroupMemberAddedUpdate update, @NonNull List<UpdateDescription> updates) {
boolean newMemberIsYou = selfIds.matches(update.newMemberAci);
if (update.updaterAci == null) {
if (newMemberIsYou) {
updates.add(0, updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group), R.drawable.ic_update_group_add_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_joined_the_group, update.newMemberAci, R.drawable.ic_update_group_add_16));
}
} else if (update.hadOpenInvitation) {
if (selfIds.matches(update.updaterAci)) {
updates.add(updateDescription(R.string.MessageRecord_you_added_invited_member_s, update.newMemberAci, R.drawable.ic_update_group_add_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_added_invited_member_s, update.updaterAci, update.newMemberAci, R.drawable.ic_update_group_add_16));
}
} else {
if (newMemberIsYou) {
updates.add(0, updateDescription(R.string.MessageRecord_s_added_you, update.updaterAci, R.drawable.ic_update_group_add_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_added_s, update.updaterAci, update.newMemberAci, R.drawable.ic_update_group_add_16));
}
}
}
private void describeGroupMemberJoinedUpdate(@NonNull GroupMemberJoinedUpdate update, @NonNull List<UpdateDescription> updates) {
boolean newMemberIsYou = selfIds.matches(update.newMemberAci);
if (newMemberIsYou) {
updates.add(0, updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group), R.drawable.ic_update_group_add_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_joined_the_group, update.newMemberAci, R.drawable.ic_update_group_add_16));
}
}
private void describeGroupInvitationAcceptedUpdate(@NonNull GroupInvitationAcceptedUpdate update, @NonNull List<UpdateDescription> updates) {
if (selfIds.matches(update.newMemberAci)) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_accepted_invite), R.drawable.ic_update_group_accept_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_accepted_invite, update.newMemberAci, R.drawable.ic_update_group_accept_16));
}
}
private void describeUnknownUsersInvitedUpdate(@NonNull GroupUnknownInviteeUpdate update, @NonNull List<UpdateDescription> updates) {
if (update.inviterAci == null) {
updates.add(updateDescription(context.getResources().getQuantityString(R.plurals.MessageRecord_d_people_were_invited_to_the_group, update.inviteeCount, update.inviteeCount), R.drawable.ic_update_group_add_16));
} else {
updates.add(updateDescription(R.plurals.MessageRecord_s_invited_members, update.inviteeCount, update.inviterAci, update.inviteeCount, R.drawable.ic_update_group_add_16));
}
}
private void describeSelfInvitedOtherUserToGroupUpdate(@NonNull SelfInvitedOtherUserToGroupUpdate update, @NonNull List<UpdateDescription> updates) {
updates.add(updateDescription(R.string.MessageRecord_you_invited_s_to_the_group, update.inviteeServiceId, R.drawable.ic_update_group_add_16));
}
private void describeSelfInvitedToGroupUpdate(@NonNull SelfInvitedToGroupUpdate update, @NonNull List<UpdateDescription> updates) {
if (update.inviterAci == null) {
updates.add(0, updateDescription(context.getString(R.string.MessageRecord_you_were_invited_to_the_group), R.drawable.ic_update_group_add_16));
} else {
updates.add(0, updateDescription(R.string.MessageRecord_s_invited_you_to_the_group, update.inviterAci, R.drawable.ic_update_group_add_16));
}
}
private void describeGenericGroupUpdate(@NonNull GenericGroupUpdate update, @NonNull List<UpdateDescription> updates) {
if (update.updaterAci == null) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_was_updated), R.drawable.ic_update_group_16));
} else {
boolean editorIsYou = selfIds.matches(update.updaterAci);
if (editorIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_updated_group), R.drawable.ic_update_group_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_updated_group, update.updaterAci, R.drawable.ic_update_group_16));
}
}
}
private void describeGroupCreationUpdate(@NonNull GroupCreationUpdate update, @NonNull List<UpdateDescription> updates) {
if (update.updaterAci == null) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_group_updated), R.drawable.ic_update_group_16));
} else {
if (selfIds.matches(update.updaterAci)) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_created_the_group), R.drawable.ic_update_group_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_added_you, update.updaterAci, R.drawable.ic_update_group_add_16));
}
}
}
private void describeGroupNameUpdate(@NonNull GroupNameUpdate update, @NonNull List<UpdateDescription> updates) {
if (update.updaterAci == null) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_name_has_changed_to_s, StringUtil.isolateBidi(update.newGroupName)), R.drawable.ic_update_group_name_16));
} else {
String newTitle = StringUtil.isolateBidi(update.newGroupName);
if (selfIds.matches(update.updaterAci)) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_changed_the_group_name_to_s, newTitle), R.drawable.ic_update_group_name_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_changed_the_group_name_to_s, update.updaterAci, newTitle, R.drawable.ic_update_group_name_16));
}
}
}
private void describeGroupMembershipAccessLevelChange(@NonNull GroupMembershipAccessLevelChangeUpdate update, @NonNull List<UpdateDescription> updates) {
if (update.accessLevel == GroupV2AccessLevel.UNKNOWN) {
return;
}
String accessLevel = GV2AccessLevelUtil.toString(context, backupGv2AccessLevelToGroups(update.accessLevel));
if (update.updaterAci == null) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_who_can_edit_group_membership_has_been_changed_to_s, accessLevel), R.drawable.ic_update_group_role_16));
} else {
boolean editorIsYou = selfIds.matches(update.updaterAci);
if (editorIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_changed_who_can_edit_group_membership_to_s, accessLevel), R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_changed_who_can_edit_group_membership_to_s, update.updaterAci, accessLevel, R.drawable.ic_update_group_role_16));
}
}
}
private void describeGroupAttributesAccessLevelChange(@NonNull GroupAttributesAccessLevelChangeUpdate update, @NonNull List<UpdateDescription> updates) {
if (update.accessLevel == GroupV2AccessLevel.UNKNOWN) {
return;
}
String accessLevel = GV2AccessLevelUtil.toString(context, backupGv2AccessLevelToGroups(update.accessLevel));
if (update.updaterAci == null) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_who_can_edit_group_info_has_been_changed_to_s, accessLevel), R.drawable.ic_update_group_role_16));
} else {
boolean editorIsYou = selfIds.matches(update.updaterAci);
if (editorIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_changed_who_can_edit_group_info_to_s, accessLevel), R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_changed_who_can_edit_group_info_to_s, update.updaterAci, accessLevel, R.drawable.ic_update_group_role_16));
}
}
}
private void describeGroupAnnouncementOnlyUpdate(@NonNull GroupAnnouncementOnlyChangeUpdate update, @NonNull List<UpdateDescription> updates) {
if (update.updaterAci == null) {
if (update.isAnnouncementOnly) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_allow_only_admins_to_send), R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(context.getString(R.string.MessageRecord_allow_all_members_to_send), R.drawable.ic_update_group_role_16));
}
} else {
boolean editorIsYou = selfIds.matches(update.updaterAci);
if (update.isAnnouncementOnly) {
if (editorIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_allow_only_admins_to_send), R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_allow_only_admins_to_send, update.updaterAci, R.drawable.ic_update_group_role_16));
}
} else {
if (editorIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_allow_all_members_to_send), R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_allow_all_members_to_send, update.updaterAci, R.drawable.ic_update_group_role_16));
}
}
}
}
private void describeGroupMemberLeftChange(@NonNull GroupMemberLeftUpdate update, @NonNull List<UpdateDescription> updates) {
if (update.aci == null) {
return;
}
boolean editorIsYou = selfIds.matches(update.aci);
if (editorIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_left_the_group), R.drawable.ic_update_group_leave_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_left_the_group, update.aci, R.drawable.ic_update_group_leave_16));
}
}
private void describeGroupMemberRemovedChange(@NonNull GroupMemberRemovedUpdate update, @NonNull List<UpdateDescription> updates) {
if (update.removerAci == null) {
boolean removedMemberIsYou = selfIds.matches(update.removedAci);
if (removedMemberIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_are_no_longer_in_the_group), R.drawable.ic_update_group_leave_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_is_no_longer_in_the_group, update.removedAci, R.drawable.ic_update_group_leave_16));
}
} else {
boolean editorIsYou = selfIds.matches(update.removerAci);
boolean removedMemberIsYou = selfIds.matches(update.removedAci);
if (editorIsYou) {
if (removedMemberIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_left_the_group), R.drawable.ic_update_group_leave_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_you_removed_s, update.removedAci, R.drawable.ic_update_group_remove_16));
}
} else {
if (removedMemberIsYou) {
updates.add(updateDescription(R.string.MessageRecord_s_removed_you_from_the_group, update.removerAci, R.drawable.ic_update_group_remove_16));
} else {
if (update.removerAci.equals(update.removedAci)) {
updates.add(updateDescription(R.string.MessageRecord_s_left_the_group, update.removedAci, R.drawable.ic_update_group_leave_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_removed_s, update.removerAci, update.removedAci, R.drawable.ic_update_group_remove_16));
}
}
}
}
}
private AccessControl.AccessRequired backupGv2AccessLevelToGroups(@NonNull GroupV2AccessLevel accessLevel) {
switch (accessLevel) {
case ANY: return AccessControl.AccessRequired.ANY;
case MEMBER: return AccessControl.AccessRequired.MEMBER;
case ADMINISTRATOR: return AccessControl.AccessRequired.ADMINISTRATOR;
case UNSATISFIABLE: return AccessControl.AccessRequired.UNSATISFIABLE;
default:
case UNKNOWN: return AccessControl.AccessRequired.UNKNOWN;
}
}
List<UpdateDescription> describeChanges(@Nullable DecryptedGroup previousGroupState, @NonNull DecryptedGroupChange change) {
if (new DecryptedGroup().equals(previousGroupState)) {
previousGroupState = null;
@ -317,6 +832,51 @@ final class GroupsV2UpdateMessageProducer {
}
}
private void describeAdminStatusChange(@NonNull GroupAdminStatusUpdate groupAdminStatusUpdate, List<UpdateDescription> updates) {
boolean changedMemberIsYou = selfIds.matches(groupAdminStatusUpdate.memberAci);
if (groupAdminStatusUpdate.updaterAci != null) {
boolean editorIsYou = selfIds.matches(groupAdminStatusUpdate.updaterAci);
if (groupAdminStatusUpdate.wasAdminStatusGranted) {
if (editorIsYou) {
updates.add(updateDescription(R.string.MessageRecord_you_made_s_an_admin, groupAdminStatusUpdate.memberAci, R.drawable.ic_update_group_role_16));
} else {
if (changedMemberIsYou) {
updates.add(updateDescription(R.string.MessageRecord_s_made_you_an_admin, groupAdminStatusUpdate.updaterAci, R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_made_s_an_admin, groupAdminStatusUpdate.updaterAci, groupAdminStatusUpdate.memberAci, R.drawable.ic_update_group_role_16));
}
}
} else {
if (editorIsYou) {
updates.add(updateDescription(R.string.MessageRecord_you_revoked_admin_privileges_from_s, groupAdminStatusUpdate.memberAci, R.drawable.ic_update_group_role_16));
} else {
if (changedMemberIsYou) {
updates.add(updateDescription(R.string.MessageRecord_s_revoked_your_admin_privileges, groupAdminStatusUpdate.updaterAci, R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_revoked_admin_privileges_from_s, groupAdminStatusUpdate.updaterAci, groupAdminStatusUpdate.memberAci, R.drawable.ic_update_group_role_16));
}
}
}
} else {
if (groupAdminStatusUpdate.wasAdminStatusGranted) {
if (changedMemberIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_are_now_an_admin), R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_is_now_an_admin, groupAdminStatusUpdate.memberAci, R.drawable.ic_update_group_role_16));
}
} else {
if (changedMemberIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_are_no_longer_an_admin), R.drawable.ic_update_group_role_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_is_no_longer_an_admin, groupAdminStatusUpdate.memberAci, R.drawable.ic_update_group_role_16));
}
}
}
}
private void describeInvitations(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
boolean editorIsYou = selfIds.matches(change.editorServiceIdBytes);
int notYouInviteCount = 0;
@ -481,6 +1041,20 @@ final class GroupsV2UpdateMessageProducer {
}
}
private void describeDescriptionChange(@NonNull GroupDescriptionUpdate groupDescriptionUpdate, @NonNull List<UpdateDescription> updates) {
if (groupDescriptionUpdate.updaterAci != null) {
boolean editorIsYou = selfIds.matches(groupDescriptionUpdate.updaterAci);
if (editorIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_changed_the_group_description), R.drawable.ic_update_group_name_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_changed_the_group_description, groupDescriptionUpdate.updaterAci, R.drawable.ic_update_group_name_16));
}
} else {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_name_has_changed_to_s, StringUtil.isolateBidi(groupDescriptionUpdate.newDescription)), R.drawable.ic_update_group_name_16));
}
}
private void describeUnknownEditorNewDescription(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
if (change.newDescription != null) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_description_has_changed), R.drawable.ic_update_group_name_16));
@ -499,6 +1073,20 @@ final class GroupsV2UpdateMessageProducer {
}
}
private void describeAvatarChange(@NonNull GroupAvatarUpdate groupAvatarUpdate, @NonNull List<UpdateDescription> updates) {
if (groupAvatarUpdate.updaterAci != null) {
boolean editorIsYou = selfIds.matches(groupAvatarUpdate.updaterAci);
if (editorIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_changed_the_group_avatar), R.drawable.ic_update_group_avatar_16));
} else {
updates.add(updateDescription(R.string.MessageRecord_s_changed_the_group_avatar, groupAvatarUpdate.updaterAci, R.drawable.ic_update_group_avatar_16));
}
} else {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_group_avatar_has_been_changed), R.drawable.ic_update_group_avatar_16));
}
}
private void describeUnknownEditorNewAvatar(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
if (change.newAvatar != null) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_group_avatar_has_been_changed), R.drawable.ic_update_group_avatar_16));

View file

@ -56,7 +56,8 @@ public class InMemoryMessageRecord extends MessageRecord {
false,
-1,
null,
0);
0,
null);
}
@Override

View file

@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails;
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras;
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails;
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent;
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent;
@ -104,6 +105,7 @@ public abstract class MessageRecord extends DisplayRecord {
private final long receiptTimestamp;
private final MessageId originalMessageId;
private final int revisionNumber;
private final MessageExtras messageExtras;
protected Boolean isJumboji = null;
@ -123,7 +125,8 @@ public abstract class MessageRecord extends DisplayRecord {
boolean viewed,
long receiptTimestamp,
@Nullable MessageId originalMessageId,
int revisionNumber)
int revisionNumber,
@Nullable MessageExtras messageExtras)
{
super(body, fromRecipient, toRecipient, dateSent, dateReceived,
threadId, deliveryStatus, hasDeliveryReceipt, type,
@ -143,6 +146,7 @@ public abstract class MessageRecord extends DisplayRecord {
this.receiptTimestamp = receiptTimestamp;
this.originalMessageId = originalMessageId;
this.revisionNumber = revisionNumber;
this.messageExtras = messageExtras;
}
public abstract boolean isMms();
@ -287,6 +291,10 @@ public abstract class MessageRecord extends DisplayRecord {
return selfCreatedGroup(change);
}
@Nullable public MessageExtras getMessageExtras() {
return messageExtras;
}
@VisibleForTesting
@Nullable DecryptedGroupV2Context getDecryptedGroupV2Context() {
if (!isGroupUpdate() || !isGroupV2()) {
@ -315,6 +323,30 @@ public abstract class MessageRecord extends DisplayRecord {
try {
byte[] decoded = Base64.decode(body);
DecryptedGroupV2Context decryptedGroupV2Context = DecryptedGroupV2Context.ADAPTER.decode(decoded);
return getGv2ChangeDescription(context, decryptedGroupV2Context, recipientClickHandler);
} catch (IOException | IllegalArgumentException | IllegalStateException e) {
Log.w(TAG, "GV2 Message update detail could not be read", e);
return staticUpdateDescription(context.getString(R.string.MessageRecord_group_updated), R.drawable.ic_update_group_16);
}
}
public static @NonNull UpdateDescription getGv2ChangeDescription(@NonNull Context context, @NonNull MessageExtras messageExtras, @Nullable Consumer<RecipientId> recipientClickHandler) {
if (messageExtras.gv2UpdateDescription != null) {
if (messageExtras.gv2UpdateDescription.groupChangeUpdate != null) {
GroupsV2UpdateMessageProducer updateMessageProducer = new GroupsV2UpdateMessageProducer(context, SignalStore.account().getServiceIds(), recipientClickHandler);
return UpdateDescription.concatWithNewLines(updateMessageProducer.describeChanges(messageExtras.gv2UpdateDescription.groupChangeUpdate.updates));
} else if (messageExtras.gv2UpdateDescription.gv2ChangeDescription != null) {
return getGv2ChangeDescription(context, messageExtras.gv2UpdateDescription.gv2ChangeDescription, recipientClickHandler);
} else {
Log.w(TAG, "GV2 Update Description missing group change update!");
}
}
return staticUpdateDescription(context.getString(R.string.MessageRecord_group_updated), R.drawable.ic_update_group_16);
}
public static @NonNull UpdateDescription getGv2ChangeDescription(@NonNull Context context, @NonNull DecryptedGroupV2Context decryptedGroupV2Context, @Nullable Consumer<RecipientId> recipientClickHandler) {
try {
GroupsV2UpdateMessageProducer updateMessageProducer = new GroupsV2UpdateMessageProducer(context, SignalStore.account().getServiceIds(), recipientClickHandler);
if (decryptedGroupV2Context.change != null && ((decryptedGroupV2Context.groupState != null && decryptedGroupV2Context.groupState.revision != 0) || decryptedGroupV2Context.previousGroupState != null)) {
@ -332,7 +364,7 @@ public abstract class MessageRecord extends DisplayRecord {
}
return UpdateDescription.concatWithNewLines(newGroupDescriptions);
}
} catch (IOException | IllegalArgumentException | IllegalStateException e) {
} catch (IllegalArgumentException | IllegalStateException e) {
Log.w(TAG, "GV2 Message update detail could not be read", e);
return staticUpdateDescription(context.getString(R.string.MessageRecord_group_updated), R.drawable.ic_update_group_16);
}

View file

@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
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.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
@ -111,12 +112,13 @@ public class MmsMessageRecord extends MessageRecord {
@Nullable MessageId latestRevisionId,
@Nullable MessageId originalMessageId,
int revisionNumber,
boolean isRead)
boolean isRead,
@Nullable MessageExtras messageExtras)
{
super(id, body, fromRecipient, fromDeviceId, toRecipient,
dateSent, dateReceived, dateServer, threadId, Status.STATUS_NONE, hasDeliveryReceipt,
mailbox, mismatches, failures, subscriptionId, expiresIn, expireStarted, hasReadReceipt,
unidentified, reactions, remoteDelete, notifiedTimestamp, viewed, receiptTimestamp, originalMessageId, revisionNumber);
unidentified, reactions, remoteDelete, notifiedTimestamp, viewed, receiptTimestamp, originalMessageId, revisionNumber, messageExtras);
this.slideDeck = slideDeck;
this.quote = quote;
@ -299,7 +301,7 @@ public class MmsMessageRecord extends MessageRecord {
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead());
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
}
public @NonNull MmsMessageRecord withoutQuote() {
@ -307,7 +309,7 @@ public class MmsMessageRecord extends MessageRecord {
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
hasReadReceipt(), null, getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead());
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
}
public @NonNull MmsMessageRecord withAttachments(@NonNull List<DatabaseAttachment> attachments) {
@ -329,7 +331,7 @@ public class MmsMessageRecord extends MessageRecord {
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
hasReadReceipt(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead());
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
}
public @NonNull MmsMessageRecord withPayment(@NonNull Payment payment) {
@ -337,7 +339,7 @@ public class MmsMessageRecord extends MessageRecord {
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment, getCall(), getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead());
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
}
@ -346,7 +348,7 @@ public class MmsMessageRecord extends MessageRecord {
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), isViewOnce(),
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), isRemoteDelete(), mentionsSelf,
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), call, getScheduledDate(), getLatestRevisionId(),
getOriginalMessageId(), getRevisionNumber(), isRead());
getOriginalMessageId(), getRevisionNumber(), isRead(), getMessageExtras());
}
private static @NonNull List<Contact> updateContacts(@NonNull List<Contact> contacts, @NonNull Map<AttachmentId, DatabaseAttachment> attachmentIdMap) {

View file

@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.database.MessageTypes;
import org.thoughtcrime.securesms.database.ThreadTable;
import org.thoughtcrime.securesms.database.ThreadTable.Extra;
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.util.Preconditions;
@ -37,26 +38,27 @@ import java.util.Objects;
*/
public final class ThreadRecord {
private final long threadId;
private final String body;
private final Recipient recipient;
private final long type;
private final long date;
private final long deliveryStatus;
private final boolean hasDeliveryReceipt;
private final boolean hasReadReceipt;
private final Uri snippetUri;
private final String contentType;
private final Extra extra;
private final boolean meaningfulMessages;
private final int unreadCount;
private final boolean forcedUnread;
private final int distributionType;
private final boolean archived;
private final long expiresIn;
private final long lastSeen;
private final boolean isPinned;
private final int unreadSelfMentionsCount;
private final long threadId;
private final String body;
private final Recipient recipient;
private final long type;
private final long date;
private final long deliveryStatus;
private final boolean hasDeliveryReceipt;
private final boolean hasReadReceipt;
private final Uri snippetUri;
private final String contentType;
private final Extra extra;
private final boolean meaningfulMessages;
private final int unreadCount;
private final boolean forcedUnread;
private final int distributionType;
private final boolean archived;
private final long expiresIn;
private final long lastSeen;
private final boolean isPinned;
private final int unreadSelfMentionsCount;
private final MessageExtras messageExtras;
private ThreadRecord(@NonNull Builder builder) {
this.threadId = builder.threadId;
@ -79,6 +81,7 @@ public final class ThreadRecord {
this.lastSeen = builder.lastSeen;
this.isPinned = builder.isPinned;
this.unreadSelfMentionsCount = builder.unreadSelfMentionsCount;
this.messageExtras = builder.messageExtras;
}
public long getThreadId() {
@ -189,6 +192,10 @@ public final class ThreadRecord {
return extra != null && extra.isScheduled();
}
public @Nullable MessageExtras getMessageExtras() {
return messageExtras;
}
public @Nullable RecipientId getGroupAddedBy() {
if (extra != null && extra.getGroupAddedBy() != null) return RecipientId.from(extra.getGroupAddedBy());
else return null;
@ -287,26 +294,27 @@ public final class ThreadRecord {
}
public static class Builder {
private long threadId;
private String body;
private Recipient recipient = Recipient.UNKNOWN;
private long type;
private long date;
private long deliveryStatus;
private boolean hasDeliveryReceipt;
private boolean hasReadReceipt;
private Uri snippetUri;
private String contentType;
private Extra extra;
private boolean meaningfulMessages;
private int unreadCount;
private boolean forcedUnread;
private int distributionType;
private boolean archived;
private long expiresIn;
private long lastSeen;
private boolean isPinned;
private int unreadSelfMentionsCount;
private long threadId;
private String body;
private Recipient recipient = Recipient.UNKNOWN;
private long type;
private long date;
private long deliveryStatus;
private boolean hasDeliveryReceipt;
private boolean hasReadReceipt;
private Uri snippetUri;
private String contentType;
private Extra extra;
private boolean meaningfulMessages;
private int unreadCount;
private boolean forcedUnread;
private int distributionType;
private boolean archived;
private long expiresIn;
private long lastSeen;
private boolean isPinned;
private int unreadSelfMentionsCount;
private MessageExtras messageExtras;
public Builder(long threadId) {
this.threadId = threadId;
@ -407,6 +415,11 @@ public final class ThreadRecord {
return this;
}
public Builder setSnippetMessageExtras(@Nullable MessageExtras messageExtras) {
this.messageExtras = messageExtras;
return this;
}
public Builder setUnreadSelfMentionsCount(int unreadSelfMentionsCount) {
this.unreadSelfMentionsCount = unreadSelfMentionsCount;
return this;

View file

@ -7,7 +7,6 @@ option java_package = "org.thoughtcrime.securesms.backup.v2.proto";
message BackupInfo {
uint64 version = 1;
uint64 backupTimeMs = 2;
bytes iv = 3;
}
message Frame {
@ -207,6 +206,9 @@ message ChatItem {
repeated SendStatus sendStatus = 1;
}
message DirectionlessMessageDetails {
}
uint64 chatId = 1; // conversation id
uint64 authorId = 2; // recipient id
uint64 dateSent = 3;
@ -217,8 +219,9 @@ message ChatItem {
bool sms = 8;
oneof directionalDetails {
IncomingMessageDetails incoming = 10;
OutgoingMessageDetails outgoing = 12;
IncomingMessageDetails incoming = 9;
OutgoingMessageDetails outgoing = 10;
DirectionlessMessageDetails directionless = 11;
}
oneof item {
@ -415,16 +418,17 @@ message FilePointer {
optional bytes key = 5;
optional string contentType = 6;
// Size of fullsize decrypted media blob in bytes.
// Can be ignored if unset/unavailable.
optional uint32 size = 7;
optional bytes digest = 8;
optional bytes incrementalMac = 9;
optional bytes incrementalMacChunkSize = 10;
optional string fileName = 11;
optional uint32 flags = 12;
optional uint32 width = 13;
optional uint32 height = 14;
optional string caption = 15;
optional string blurHash = 16;
optional bytes incrementalMac = 8;
optional bytes incrementalMacChunkSize = 9;
optional string fileName = 10;
optional uint32 flags = 11;
optional uint32 width = 12;
optional uint32 height = 13;
optional string caption = 14;
optional string blurHash = 15;
}
message Quote {
@ -478,7 +482,7 @@ message Reaction {
message ChatUpdateMessage {
oneof update {
SimpleChatUpdate simpleUpdate = 1;
GroupDescriptionChatUpdate groupDescription = 2;
GroupChangeChatUpdate groupChange = 2;
ExpirationTimerChatUpdate expirationTimerChange = 3;
ProfileChangeChatUpdate profileChange = 4;
ThreadMergeChatUpdate threadMerge = 5;
@ -533,10 +537,6 @@ message SimpleChatUpdate {
Type type = 1;
}
message GroupDescriptionChatUpdate {
string newDescription = 1;
}
message ExpirationTimerChatUpdate {
uint32 expiresInMs = 1;
}
@ -554,6 +554,246 @@ message SessionSwitchoverChatUpdate {
uint64 e164 = 1;
}
message GroupChangeChatUpdate {
message Update {
// Note: group expiration timer changes are represented as ExpirationTimerChatUpdate.
oneof update {
GenericGroupUpdate genericGroupUpdate = 1;
GroupCreationUpdate groupCreationUpdate = 2;
GroupNameUpdate groupNameUpdate = 3;
GroupAvatarUpdate groupAvatarUpdate = 4;
GroupDescriptionUpdate groupDescriptionUpdate = 5;
GroupMembershipAccessLevelChangeUpdate groupMembershipAccessLevelChangeUpdate = 6;
GroupAttributesAccessLevelChangeUpdate groupAttributesAccessLevelChangeUpdate = 7;
GroupAnnouncementOnlyChangeUpdate groupAnnouncementOnlyChangeUpdate = 8;
GroupAdminStatusUpdate groupAdminStatusUpdate = 9;
GroupMemberLeftUpdate groupMemberLeftUpdate = 10;
GroupMemberRemovedUpdate groupMemberRemovedUpdate = 11;
SelfInvitedToGroupUpdate selfInvitedToGroupUpdate = 12;
SelfInvitedOtherUserToGroupUpdate selfInvitedOtherUserToGroupUpdate = 13;
GroupUnknownInviteeUpdate groupUnknownInviteeUpdate = 14;
GroupInvitationAcceptedUpdate groupInvitationAcceptedUpdate = 15;
GroupInvitationDeclinedUpdate groupInvitationDeclinedUpdate = 16;
GroupMemberJoinedUpdate groupMemberJoinedUpdate = 17;
GroupMemberAddedUpdate groupMemberAddedUpdate = 18;
GroupSelfInvitationRevokedUpdate groupSelfInvitationRevokedUpdate = 19;
GroupInvitationRevokedUpdate groupInvitationRevokedUpdate = 20;
GroupJoinRequestUpdate groupJoinRequestUpdate = 21;
GroupJoinRequestApprovalUpdate groupJoinRequestApprovalUpdate = 22;
GroupJoinRequestCanceledUpdate groupJoinRequestCanceledUpdate = 23;
GroupInviteLinkResetUpdate groupInviteLinkResetUpdate = 24;
GroupInviteLinkEnabledUpdate groupInviteLinkEnabledUpdate = 25;
GroupInviteLinkAdminApprovalUpdate groupInviteLinkAdminApprovalUpdate = 26;
GroupInviteLinkDisabledUpdate groupInviteLinkDisabledUpdate = 27;
GroupMemberJoinedByLinkUpdate groupMemberJoinedByLinkUpdate = 28;
GroupV2MigrationUpdate groupV2MigrationUpdate = 29;
GroupV2MigrationSelfInvitedUpdate groupV2MigrationSelfInvitedUpdate = 30;
GroupV2MigrationInvitedMembersUpdate groupV2MigrationInvitedMembersUpdate = 31;
GroupV2MigrationDroppedMembersUpdate groupV2MigrationDroppedMembersUpdate = 32;
GroupSequenceOfRequestsAndCancelsUpdate groupSequenceOfRequestsAndCancelsUpdate = 33;
}
}
// Must be one or more; all updates batched together came from
// a single batched group state update.
repeated Update updates = 1;
}
message GenericGroupUpdate {
optional bytes updaterAci = 1;
}
message GroupCreationUpdate {
optional bytes updaterAci = 1;
}
message GroupNameUpdate {
optional bytes updaterAci = 1;
// Null value means the group name was removed.
optional string newGroupName = 2;
}
message GroupAvatarUpdate {
optional bytes updaterAci = 1;
bool wasRemoved = 2;
}
message GroupDescriptionUpdate {
optional bytes updaterAci = 1;
// Null value means the group description was removed.
optional string newDescription = 2;
}
enum GroupV2AccessLevel {
UNKNOWN = 0;
ANY = 1;
MEMBER = 2;
ADMINISTRATOR = 3;
UNSATISFIABLE = 4;
}
message GroupMembershipAccessLevelChangeUpdate {
optional bytes updaterAci = 1;
GroupV2AccessLevel accessLevel = 2;
}
message GroupAttributesAccessLevelChangeUpdate {
optional bytes updaterAci = 1;
GroupV2AccessLevel accessLevel = 2;
}
message GroupAnnouncementOnlyChangeUpdate {
optional bytes updaterAci = 1;
bool isAnnouncementOnly = 2;
}
message GroupAdminStatusUpdate {
optional bytes updaterAci = 1;
// The aci who had admin status granted or revoked.
bytes memberAci = 2;
bool wasAdminStatusGranted = 3;
}
message GroupMemberLeftUpdate {
optional bytes aci = 1;
}
message GroupMemberRemovedUpdate {
optional bytes removerAci = 1;
bytes removedAci = 2;
}
message SelfInvitedToGroupUpdate {
optional bytes inviterAci = 1;
}
message SelfInvitedOtherUserToGroupUpdate {
// If no invitee id available, use GroupUnknownInviteeUpdate
bytes inviteeServiceId = 1;
}
message GroupUnknownInviteeUpdate {
// Can be the self user.
optional bytes inviterAci = 1;
uint32 inviteeCount = 2;
}
message GroupInvitationAcceptedUpdate {
optional bytes inviterAci = 1;
bytes newMemberAci = 2;
}
message GroupInvitationDeclinedUpdate {
optional bytes inviterAci = 1;
// Note: if invited by pni, just set inviteeAci to nil.
optional bytes inviteeAci = 2;
}
message GroupMemberJoinedUpdate {
bytes newMemberAci = 1;
}
message GroupMemberAddedUpdate {
optional bytes updaterAci = 1;
bytes newMemberAci = 2;
bool hadOpenInvitation = 3;
// If hadOpenInvitation is true, optionally include aci of the inviter.
optional bytes inviterAci = 4;
}
// An invitation to self was revoked.
message GroupSelfInvitationRevokedUpdate {
optional bytes revokerAci = 1;
}
// These invitees should never be the local user.
// Use GroupSelfInvitationRevokedUpdate in those cases.
// The inviter or updater can be the local user.
message GroupInvitationRevokedUpdate {
message Invitee {
optional bytes inviterAci = 1;
// Prefer to use aci over pni. No need to set
// pni if aci is set. Both can be missing.
optional bytes inviteeAci = 2;
optional bytes inviteePni = 3;
}
// The member that revoked the invite(s), not the inviter!
// Assumed to be an admin (at the time, may no longer be an
// admin or even a member).
optional bytes updaterAci = 1;
repeated Invitee invitees = 2;
}
message GroupJoinRequestUpdate {
bytes requestorAci = 1;
}
message GroupJoinRequestApprovalUpdate {
bytes requestorAci = 1;
// The aci that approved or rejected the request.
optional bytes updaterAci = 2;
bool wasApproved = 3;
}
message GroupJoinRequestCanceledUpdate {
bytes requestorAci = 1;
}
// A single requestor has requested to join and cancelled
// their request repeatedly with no other updates in between.
// The last action encompassed by this update is always a
// cancellation; if there was another open request immediately
// after, it will be a separate GroupJoinRequestUpdate, either
// in the same frame or in a subsequent frame.
message GroupSequenceOfRequestsAndCancelsUpdate {
bytes requestorAci = 1;
uint32 count = 2;
}
message GroupInviteLinkResetUpdate {
optional bytes updaterAci = 1;
}
message GroupInviteLinkEnabledUpdate {
optional bytes updaterAci = 1;
bool linkRequiresAdminApproval = 2;
}
message GroupInviteLinkAdminApprovalUpdate {
optional bytes updaterAci = 1;
bool linkRequiresAdminApproval = 2;
}
message GroupInviteLinkDisabledUpdate {
optional bytes updaterAci = 1;
}
message GroupMemberJoinedByLinkUpdate {
bytes newMemberAci = 1;
}
// A gv1->gv2 migration occurred.
message GroupV2MigrationUpdate {}
// Another user migrated gv1->gv2 but was unable to add
// the local user and invited them instead.
message GroupV2MigrationSelfInvitedUpdate {}
// The local user migrated gv1->gv2 but was unable to
// add some members and invited them instead.
// (Happens if we don't have the invitee's profile key)
message GroupV2MigrationInvitedMembersUpdate {
int32 invitedMembersCount = 1;
}
// The local user migrated gv1->gv2 but was unable to
// add or invite some members and dropped them instead.
// (Happens for e164 members where we don't have an aci).
message GroupV2MigrationDroppedMembersUpdate {
int32 droppedMembersCount = 1;
}
message StickerPack {
bytes id = 1;
bytes key = 2;

View file

@ -11,6 +11,8 @@ package signal;
option java_package = "org.thoughtcrime.securesms.database.model.databaseprotos";
option java_multiple_files = true;
import Backup.proto;
// DEPRECATED -- only here for database migrations
message ReactionList {
option deprecated = true;
@ -371,3 +373,12 @@ message ExternalLaunchTransactionState {
GatewayRequest gatewayRequest = 2;
string paymentSourceType = 3;
}
message MessageExtras {
GV2UpdateDescription gv2UpdateDescription = 1;
}
message GV2UpdateDescription {
optional DecryptedGroupV2Context gv2ChangeDescription = 1;
backup.GroupChangeChatUpdate groupChangeUpdate = 2;
}

View file

@ -183,7 +183,8 @@ object FakeMessageRecords {
null,
null,
0,
false
false,
null
)
}
}