mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-22 02:36:55 +00:00
Collapse multiple join request/cancels when from a single person.
This commit is contained in:
@@ -91,7 +91,7 @@ public class GroupDatabase extends Database {
|
||||
/** Increments with every change to the group */
|
||||
private static final String V2_REVISION = "revision";
|
||||
/** Serialized {@link DecryptedGroup} protobuf */
|
||||
private static final String V2_DECRYPTED_GROUP = "decrypted_group";
|
||||
public static final String V2_DECRYPTED_GROUP = "decrypted_group";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
||||
GROUP_ID + " TEXT, " +
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.android.mms.pdu_alt.NotificationInd;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import net.zetetic.database.sqlcipher.SQLiteStatement;
|
||||
|
||||
@@ -1093,6 +1094,8 @@ public class SmsDatabase extends MessageDatabase {
|
||||
|
||||
@Override
|
||||
public Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, long type) {
|
||||
boolean tryToCollapseJoinRequestEvents = false;
|
||||
|
||||
if (message.isJoined()) {
|
||||
type = (type & (Types.TOTAL_MASK - Types.BASE_TYPE_MASK)) | Types.JOINED_TYPE;
|
||||
} else if (message.isPreKeyBundle()) {
|
||||
@@ -1108,6 +1111,8 @@ public class SmsDatabase extends MessageDatabase {
|
||||
type |= Types.GROUP_V2_BIT | Types.GROUP_UPDATE_BIT;
|
||||
if (incomingGroupUpdateMessage.isJustAGroupLeave()) {
|
||||
type |= Types.GROUP_LEAVE_BIT;
|
||||
} else if (incomingGroupUpdateMessage.isCancelJoinRequest()) {
|
||||
tryToCollapseJoinRequestEvents = true;
|
||||
}
|
||||
} else if (incomingGroupUpdateMessage.isUpdate()) {
|
||||
type |= Types.GROUP_UPDATE_BIT;
|
||||
@@ -1152,6 +1157,13 @@ public class SmsDatabase extends MessageDatabase {
|
||||
if (groupRecipient == null) threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
|
||||
else threadId = SignalDatabase.threads().getOrCreateThreadIdFor(groupRecipient);
|
||||
|
||||
if (tryToCollapseJoinRequestEvents) {
|
||||
final Optional<InsertResult> result = collapseJoinRequestEventsIfPossible(threadId, (IncomingGroupUpdateMessage) message);
|
||||
if (result.isPresent()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(RECIPIENT_ID, message.getSender().serialize());
|
||||
values.put(ADDRESS_DEVICE_ID, message.getSenderDeviceId());
|
||||
@@ -1809,4 +1821,44 @@ public class SmsDatabase extends MessageDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Optional<InsertResult> collapseJoinRequestEventsIfPossible(long threadId, IncomingGroupUpdateMessage message) {
|
||||
InsertResult result = null;
|
||||
|
||||
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.beginTransaction();
|
||||
|
||||
try {
|
||||
try (MmsSmsDatabase.Reader reader = MmsSmsDatabase.readerFor(SignalDatabase.mmsSms().getConversation(threadId, 0, 2))) {
|
||||
MessageRecord latestMessage = reader.getNext();
|
||||
if (latestMessage != null && latestMessage.isGroupV2()) {
|
||||
Optional<ByteString> changeEditor = message.getChangeEditor();
|
||||
if (changeEditor.isPresent() && latestMessage.isGroupV2JoinRequest(changeEditor.get())) {
|
||||
String encodedBody;
|
||||
long id;
|
||||
|
||||
MessageRecord secondLatestMessage = reader.getNext();
|
||||
if (secondLatestMessage != null && secondLatestMessage.isGroupV2JoinRequest(changeEditor.get())) {
|
||||
id = secondLatestMessage.getId();
|
||||
encodedBody = MessageRecord.createNewContextWithAppendedDeleteJoinRequest(secondLatestMessage, message.getChangeRevision(), changeEditor.get());
|
||||
deleteMessage(latestMessage.getId());
|
||||
} else {
|
||||
id = latestMessage.getId();
|
||||
encodedBody = MessageRecord.createNewContextWithAppendedDeleteJoinRequest(latestMessage, message.getChangeRevision(), changeEditor.get());
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(BODY, encodedBody);
|
||||
getWritableDatabase().update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(id));
|
||||
result = new InsertResult(id, threadId);
|
||||
}
|
||||
}
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
return Optional.ofNullable(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,13 @@ import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
final class GroupsV2UpdateMessageProducer {
|
||||
|
||||
@@ -639,13 +642,22 @@ final class GroupsV2UpdateMessageProducer {
|
||||
}
|
||||
|
||||
private void describeRequestingMembers(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
||||
Set<ByteString> deleteRequestingUuids = new HashSet<>(change.getDeleteRequestingMembersList());
|
||||
|
||||
for (DecryptedRequestingMember member : change.getNewRequestingMembersList()) {
|
||||
boolean requestingMemberIsYou = member.getUuid().equals(selfUuidBytes);
|
||||
|
||||
if (requestingMemberIsYou) {
|
||||
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(member.getUuid(), requesting -> context.getString(R.string.MessageRecord_s_requested_to_join_via_the_group_link, requesting), R.drawable.ic_update_group_16));
|
||||
if (deleteRequestingUuids.contains(member.getUuid())) {
|
||||
updates.add(updateDescription(member.getUuid(), requesting -> context.getResources().getQuantityString(R.plurals.MessageRecord_s_requested_and_cancelled_their_request_to_join_via_the_group_link,
|
||||
change.getDeleteRequestingMembersCount(),
|
||||
requesting,
|
||||
change.getDeleteRequestingMembersCount()), R.drawable.ic_update_group_16));
|
||||
} else {
|
||||
updates.add(updateDescription(member.getUuid(), requesting -> context.getString(R.string.MessageRecord_s_requested_to_join_via_the_group_link, requesting), R.drawable.ic_update_group_16));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -681,9 +693,15 @@ final class GroupsV2UpdateMessageProducer {
|
||||
}
|
||||
|
||||
private void describeRequestingMembersDeletes(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
||||
Set<ByteString> newRequestingUuids = change.getNewRequestingMembersList().stream().map(r -> r.getUuid()).collect(Collectors.toSet());
|
||||
|
||||
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||
|
||||
for (ByteString requestingMember : change.getDeleteRequestingMembersList()) {
|
||||
if (newRequestingUuids.contains(requestingMember)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean requestingMemberIsYou = requestingMember.equals(selfUuidBytes);
|
||||
|
||||
if (requestingMemberIsYou) {
|
||||
|
||||
@@ -26,10 +26,12 @@ 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.core.content.ContextCompat;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
@@ -71,6 +73,8 @@ import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
import kotlin.collections.CollectionsKt;
|
||||
|
||||
/**
|
||||
* The base class for message record models that are displayed in
|
||||
* conversations, as opposed to models that are displayed in a thread list.
|
||||
@@ -234,7 +238,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return selfCreatedGroup(change);
|
||||
}
|
||||
|
||||
private @Nullable DecryptedGroupV2Context getDecryptedGroupV2Context() {
|
||||
@VisibleForTesting
|
||||
@Nullable DecryptedGroupV2Context getDecryptedGroupV2Context() {
|
||||
if (!isGroupUpdate() || !isGroupV2()) {
|
||||
return null;
|
||||
}
|
||||
@@ -409,6 +414,31 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return "";
|
||||
}
|
||||
|
||||
public boolean isGroupV2JoinRequest(ByteString uuid) {
|
||||
DecryptedGroupV2Context decryptedGroupV2Context = getDecryptedGroupV2Context();
|
||||
if (decryptedGroupV2Context != null && decryptedGroupV2Context.hasChange()) {
|
||||
DecryptedGroupChange change = decryptedGroupV2Context.getChange();
|
||||
return change.getEditor().equals(uuid) && change.getNewRequestingMembersList().stream().anyMatch(r -> r.getUuid().equals(uuid));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static @NonNull String createNewContextWithAppendedDeleteJoinRequest(@NonNull MessageRecord messageRecord, int revision, @NonNull ByteString id) {
|
||||
DecryptedGroupV2Context decryptedGroupV2Context = messageRecord.getDecryptedGroupV2Context();
|
||||
|
||||
if (decryptedGroupV2Context != null && decryptedGroupV2Context.hasChange()) {
|
||||
DecryptedGroupChange change = decryptedGroupV2Context.getChange();
|
||||
|
||||
return Base64.encodeBytes(decryptedGroupV2Context.toBuilder()
|
||||
.setChange(change.toBuilder()
|
||||
.setRevision(revision)
|
||||
.addDeleteRequestingMembers(id))
|
||||
.build().toByteArray());
|
||||
}
|
||||
|
||||
throw new AssertionError("Attempting to modify a message with no change");
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a UUID by it's corresponding recipient's {@link Recipient#getDisplayName(Context)}.
|
||||
*/
|
||||
|
||||
@@ -2,10 +2,14 @@ package org.thoughtcrime.securesms.sms;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.thoughtcrime.securesms.mms.MessageGroupContext;
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Helper util for inspecting GV2 {@link MessageGroupContext} for various message processing.
|
||||
*/
|
||||
@@ -42,4 +46,29 @@ public final class GroupV2UpdateMessageUtil {
|
||||
.build();
|
||||
return DecryptedGroupUtil.changeIsEmpty(withoutDeletedMembers);
|
||||
}
|
||||
|
||||
public static boolean isJoinRequestCancel(@NonNull MessageGroupContext groupContext) {
|
||||
if (isGroupV2(groupContext) && isUpdate(groupContext)) {
|
||||
DecryptedGroupChange decryptedGroupChange = groupContext.requireGroupV2Properties()
|
||||
.getChange();
|
||||
|
||||
return decryptedGroupChange.getDeleteRequestingMembersCount() > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getChangeRevision(@NonNull MessageGroupContext groupContext) {
|
||||
if (isGroupV2(groupContext) && isUpdate(groupContext)) {
|
||||
return groupContext.requireGroupV2Properties().getChange().getRevision();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static Optional<ByteString> getChangeEditor(MessageGroupContext groupContext) {
|
||||
if (isGroupV2(groupContext) && isUpdate(groupContext)) {
|
||||
return Optional.ofNullable(groupContext.requireGroupV2Properties().getChange().getEditor());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||
import org.thoughtcrime.securesms.mms.MessageGroupContext;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
||||
|
||||
public final class IncomingGroupUpdateMessage extends IncomingTextMessage {
|
||||
@@ -43,4 +47,16 @@ public final class IncomingGroupUpdateMessage extends IncomingTextMessage {
|
||||
public boolean isJustAGroupLeave() {
|
||||
return GroupV2UpdateMessageUtil.isJustAGroupLeave(groupContext);
|
||||
}
|
||||
|
||||
public boolean isCancelJoinRequest() {
|
||||
return GroupV2UpdateMessageUtil.isJoinRequestCancel(groupContext);
|
||||
}
|
||||
|
||||
public int getChangeRevision() {
|
||||
return GroupV2UpdateMessageUtil.getChangeRevision(groupContext);
|
||||
}
|
||||
|
||||
public Optional<ByteString> getChangeEditor() {
|
||||
return GroupV2UpdateMessageUtil.getChangeEditor(groupContext);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user