Fix group update item bugs caused by backup support changes.

This commit is contained in:
Cody Henthorne
2025-07-03 09:48:08 -04:00
committed by Alex Hart
parent 347005bec6
commit dfdb8f699a
6 changed files with 70 additions and 29 deletions

View File

@@ -103,6 +103,8 @@ import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.database.model.StoryType.Companion.fromCode
import org.thoughtcrime.securesms.database.model.StoryViewState
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.GroupCallUpdateDetails
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExportState
@@ -3817,20 +3819,25 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
val secondLatestMessage = reader.getNext()
val id: Long
val encodedBody: String
val updatedContext: DecryptedGroupV2Context
val messageExtras: MessageExtras?
val changeRevision: Int = message.groupContext?.let { GroupV2UpdateMessageUtil.getChangeRevision(it) } ?: -1
if (secondLatestMessage != null && secondLatestMessage.isGroupV2JoinRequest(changeEditor.get())) {
id = secondLatestMessage.id
encodedBody = MessageRecord.createNewContextWithAppendedDeleteJoinRequest(secondLatestMessage, changeRevision, changeEditor.get().toByteString())
messageExtras = secondLatestMessage.messageExtras
updatedContext = MessageRecord.createNewContextWithAppendedDeleteJoinRequest(secondLatestMessage, changeRevision, changeEditor.get().toByteString())
deleteMessage(latestMessage.id)
} else {
id = latestMessage.id
encodedBody = MessageRecord.createNewContextWithAppendedDeleteJoinRequest(latestMessage, changeRevision, changeEditor.get().toByteString())
messageExtras = latestMessage.messageExtras
updatedContext = MessageRecord.createNewContextWithAppendedDeleteJoinRequest(latestMessage, changeRevision, changeEditor.get().toByteString())
}
val updatedMessageExtras = (messageExtras?.newBuilder() ?: MessageExtras.Builder()).gv2UpdateDescription(GV2UpdateDescription(gv2ChangeDescription = updatedContext)).build()
db.update(TABLE_NAME)
.values(BODY to encodedBody)
.values(MESSAGE_EXTRAS to updatedMessageExtras.encode())
.where("$ID = ?", id)
.run()

View File

@@ -24,12 +24,10 @@ import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import androidx.compose.ui.text.AnnotatedString;
import androidx.core.content.ContextCompat;
import com.annimon.stream.Stream;
@@ -40,6 +38,8 @@ import org.signal.core.util.logging.Log;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.backup.v2.proto.GroupChangeChatUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupCreationUpdate;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.thoughtcrime.securesms.components.transfercontrols.TransferControlView;
@@ -55,9 +55,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchove
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent;
import org.thoughtcrime.securesms.emoji.EmojiSource;
import org.thoughtcrime.securesms.emoji.JumboEmoji;
import org.thoughtcrime.securesms.fonts.SignalSymbols;
import org.thoughtcrime.securesms.fonts.SignalSymbols.Glyph;
import org.thoughtcrime.securesms.fonts.SignalSymbols.Weight;
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mms.Slide;
@@ -305,12 +303,22 @@ public abstract class MessageRecord extends DisplayRecord {
public boolean isSelfCreatedGroup() {
DecryptedGroupV2Context decryptedGroupV2Context = getDecryptedGroupV2Context();
if (decryptedGroupV2Context == null) {
return false;
}
DecryptedGroupChange change = decryptedGroupV2Context.change;
if (decryptedGroupV2Context != null) {
DecryptedGroupChange change = decryptedGroupV2Context.change;
return selfCreatedGroup(change);
return selfCreatedGroup(change);
}
GroupChangeChatUpdate groupChangeChatUpdate = getGroupChangeChatUpdate();
if (groupChangeChatUpdate != null) {
GroupCreationUpdate update = groupChangeChatUpdate.updates.stream().filter(u -> u.groupCreationUpdate != null).map(u -> u.groupCreationUpdate).findFirst().orElse(null);
return update != null &&
update.updaterAci != null &&
update.updaterAci.equals(SignalStore.account().requireAci().toByteString());
}
return false;
}
public @Nullable MessageExtras getMessageExtras() {
@@ -343,6 +351,18 @@ public abstract class MessageRecord extends DisplayRecord {
return decryptedGroupV2Context;
}
private @Nullable GroupChangeChatUpdate getGroupChangeChatUpdate() {
if (!isGroupUpdate() || !isGroupV2()) {
return null;
}
if (messageExtras != null && messageExtras.gv2UpdateDescription != null) {
return messageExtras.gv2UpdateDescription.groupChangeUpdate;
}
return null;
}
private static boolean selfCreatedGroup(@Nullable DecryptedGroupChange change) {
return change != null &&
change.revision == 0 &&
@@ -552,6 +572,12 @@ public abstract class MessageRecord extends DisplayRecord {
if (decryptedGroupV2Context != null) {
return decryptedGroupV2Context.change != null && decryptedGroupV2Context.change.newDescription != null;
}
GroupChangeChatUpdate updates = getGroupChangeChatUpdate();
if (updates != null) {
return updates.updates.stream().anyMatch(update -> update.groupDescriptionUpdate != null);
}
return false;
}
@@ -560,6 +586,15 @@ public abstract class MessageRecord extends DisplayRecord {
if (decryptedGroupV2Context != null && decryptedGroupV2Context.change != null) {
return decryptedGroupV2Context.change.newDescription != null ? decryptedGroupV2Context.change.newDescription.value_ : "";
}
GroupChangeChatUpdate updates = getGroupChangeChatUpdate();
if (updates != null) {
return updates.updates.stream()
.filter(u -> u.groupDescriptionUpdate != null)
.map(u -> u.groupDescriptionUpdate.newDescription)
.findFirst()
.orElse("");
}
return "";
}
@@ -594,7 +629,7 @@ public abstract class MessageRecord extends DisplayRecord {
return false;
}
public static @NonNull String createNewContextWithAppendedDeleteJoinRequest(@NonNull MessageRecord messageRecord, int revision, @NonNull ByteString id) {
public static @NonNull DecryptedGroupV2Context createNewContextWithAppendedDeleteJoinRequest(@NonNull MessageRecord messageRecord, int revision, @NonNull ByteString id) {
DecryptedGroupV2Context decryptedGroupV2Context = messageRecord.getDecryptedGroupV2Context();
if (decryptedGroupV2Context != null && decryptedGroupV2Context.change != null) {
@@ -603,13 +638,12 @@ public abstract class MessageRecord extends DisplayRecord {
List<ByteString> deleteRequestingMembers = new ArrayList<>(change.deleteRequestingMembers);
deleteRequestingMembers.add(id);
return Base64.encodeWithPadding(decryptedGroupV2Context.newBuilder()
.change(change.newBuilder()
.revision(revision)
.deleteRequestingMembers(deleteRequestingMembers)
.build())
.build()
.encode());
return decryptedGroupV2Context.newBuilder()
.change(change.newBuilder()
.revision(revision)
.deleteRequestingMembers(deleteRequestingMembers)
.build())
.build();
}
throw new AssertionError("Attempting to modify a message with no change");

View File

@@ -36,11 +36,11 @@ private fun DecryptedGroup.formatAsHtml(): String {
return """
Revision: $revision
Title: $title
Avatar: ${(avatar?.length ?: 0) != 0}
Timer: ${disappearingMessagesTimer!!.duration}
Avatar: ${(avatar.length) != 0}
Timer: ${disappearingMessagesTimer?.duration}
Description: "$description"
Announcement: $isAnnouncementGroup
Access: attributes(${accessControl!!.attributes}) members(${accessControl!!.members}) link(${accessControl!!.addFromInviteLink})
Access: attributes(${accessControl?.attributes}) members(${accessControl?.members}) link(${accessControl?.addFromInviteLink})
Members: $members
Pending: $pending
Requesting: $requesting

View File

@@ -50,7 +50,9 @@ object GV2UpdateTransformer : ColumnTransformer {
private fun messageExtrasGroupUpdate(messageExtras: MessageExtras): String {
val gv2ChangeDescription: UpdateDescription = MessageRecord.getGv2ChangeDescription(AppDependencies.application, messageExtras, null)
return "${gv2ChangeDescription.spannable}<br><br>${messageExtras.gv2UpdateDescription!!.gv2ChangeDescription!!.change}"
val gv2ChangeProto: Any? = messageExtras.gv2UpdateDescription?.gv2ChangeDescription?.change ?: messageExtras.gv2UpdateDescription?.groupChangeUpdate
return "${gv2ChangeDescription.spannable}<br><br>$gv2ChangeProto"
}
}

View File

@@ -6,7 +6,6 @@ import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.junit.Assert.assertEquals
import org.junit.Test
import org.signal.core.util.Base64
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
import org.thoughtcrime.securesms.groups.v2.ChangeBuilder
import org.whispersystems.signalservice.api.push.ServiceId.ACI
@@ -65,9 +64,7 @@ class MessageRecordTest_createNewContextWithAppendedDeleteJoinRequest {
every { decryptedGroupV2Context } returns context
}
val newEncodedBody = MessageRecord.createNewContextWithAppendedDeleteJoinRequest(messageRecord, 10, aliceByteString)
val newContext = DecryptedGroupV2Context.ADAPTER.decode(Base64.decode(newEncodedBody))
val newContext = MessageRecord.createNewContextWithAppendedDeleteJoinRequest(messageRecord, 10, aliceByteString)
assertEquals("revision updated to 10", newContext.change?.revision, 10)
assertEquals("change should retain join request", newContext.change?.newRequestingMembers?.single()?.aciBytes, aliceByteString)

View File

@@ -350,6 +350,7 @@ internal class SpinnerServer(
try {
row += transformers[i].transform(null, columnName, this)
} catch (e: Exception) {
Log.w(TAG, "Failed to transform", e)
row += "*Failed to Transform*\n\n${DefaultColumnTransformer.transform(null, columnName, this)}"
}
}