Prevent group leave event from bumping conversation.

This commit is contained in:
Cody Henthorne
2021-09-16 12:36:00 -04:00
committed by Alex Hart
parent b4465953d8
commit 5e968eb831
20 changed files with 989 additions and 51 deletions

View File

@@ -236,7 +236,9 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
final int getOutgoingSecureMessageCount(long threadId) {
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
String[] projection = new String[] {"COUNT(*)"};
String query = getOutgoingSecureMessageClause() + "AND " + MmsSmsColumns.THREAD_ID + " = ? AND" + "(" + getTypeField() + " & " + Types.GROUP_QUIT_BIT + " = 0)";
String query = getOutgoingSecureMessageClause() +
"AND " + MmsSmsColumns.THREAD_ID + " = ? " +
"AND (" + getTypeField() + " & " + Types.GROUP_LEAVE_BIT + " = 0 OR " + getTypeField() + " & " + Types.GROUP_V2_BIT + " = " + Types.GROUP_V2_BIT + ")";
String[] args = new String[]{String.valueOf(threadId)};
try (Cursor cursor = db.query(getTableName(), projection, query, args, null, null, null, null)) {

View File

@@ -528,9 +528,9 @@ public class MmsDatabase extends MessageDatabase {
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
String[] columns = new String[]{ID};
String query = ID + " = ? AND " + MESSAGE_BOX + " & ?";
long type = Types.getOutgoingEncryptedMessageType() | Types.GROUP_QUIT_BIT;
String[] args = new String[]{String.valueOf(messageId), String.valueOf(type)};
long type = Types.getOutgoingEncryptedMessageType() | Types.GROUP_LEAVE_BIT;
String query = ID + " = ? AND " + MESSAGE_BOX + " & " + type + " = " + type + " AND " + MESSAGE_BOX + " & " + Types.GROUP_V2_BIT + " = 0";
String[] args = SqlUtil.buildArgs(messageId);
try (Cursor cursor = db.query(TABLE_NAME, columns, query, args, null, null, null, null)) {
if (cursor.getCount() == 1) {
@@ -546,9 +546,9 @@ public class MmsDatabase extends MessageDatabase {
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
String[] columns = new String[]{DATE_SENT};
String query = THREAD_ID + " = ? AND " + MESSAGE_BOX + " & ? AND " + DATE_SENT + " < ?";
long type = Types.getOutgoingEncryptedMessageType() | Types.GROUP_QUIT_BIT;
String[] args = new String[]{String.valueOf(threadId), String.valueOf(type), String.valueOf(quitTimeBarrier)};
long type = Types.getOutgoingEncryptedMessageType() | Types.GROUP_LEAVE_BIT;
String query = THREAD_ID + " = ? AND " + MESSAGE_BOX + " & " + type + " = " + type + " AND " + MESSAGE_BOX + " & " + Types.GROUP_V2_BIT + " = 0 AND " + DATE_SENT + " < ?";
String[] args = new String[]{String.valueOf(threadId), String.valueOf(quitTimeBarrier)};
String orderBy = DATE_SENT + " DESC";
String limit = "1";
@@ -1490,10 +1490,13 @@ public class MmsDatabase extends MessageDatabase {
OutgoingGroupUpdateMessage outgoingGroupUpdateMessage = (OutgoingGroupUpdateMessage) message;
if (outgoingGroupUpdateMessage.isV2Group()) {
type |= Types.GROUP_V2_BIT | Types.GROUP_UPDATE_BIT;
if (outgoingGroupUpdateMessage.isJustAGroupLeave()) {
type |= Types.GROUP_LEAVE_BIT;
}
} else {
MessageGroupContext.GroupV1Properties properties = outgoingGroupUpdateMessage.requireGroupV1Properties();
if (properties.isUpdate()) type |= Types.GROUP_UPDATE_BIT;
else if (properties.isQuit()) type |= Types.GROUP_QUIT_BIT;
else if (properties.isQuit()) type |= Types.GROUP_LEAVE_BIT;
}
}

View File

@@ -118,9 +118,11 @@ public interface MmsSmsColumns {
// Group Message Information
protected static final long GROUP_UPDATE_BIT = 0x10000;
protected static final long GROUP_QUIT_BIT = 0x20000;
// Note: Leave bit was previous QUIT bit for GV1, now also general member leave for GV2
protected static final long GROUP_LEAVE_BIT = 0x20000;
protected static final long EXPIRATION_TIMER_UPDATE_BIT = 0x40000;
protected static final long GROUP_V2_BIT = 0x80000;
protected static final long GROUP_V2_LEAVE_BITS = GROUP_V2_BIT | GROUP_LEAVE_BIT | GROUP_UPDATE_BIT;
// Encrypted Storage Information XXX
public static final long ENCRYPTION_MASK = 0xFF000000;
@@ -303,7 +305,7 @@ public interface MmsSmsColumns {
}
public static boolean isGroupQuit(long type) {
return (type & GROUP_QUIT_BIT) != 0;
return (type & GROUP_LEAVE_BIT) != 0 && (type & GROUP_V2_BIT) == 0;
}
public static boolean isChatSessionRefresh(long type) {
@@ -339,6 +341,10 @@ public interface MmsSmsColumns {
return type == CHANGE_NUMBER_TYPE;
}
public static boolean isGroupV2LeaveOnly(long type) {
return (type & GROUP_V2_LEAVE_BITS) == GROUP_V2_LEAVE_BITS;
}
public static long translateFromSystemBaseType(long theirType) {
// public static final int NONE_TYPE = 0;
// public static final int INBOX_TYPE = 1;

View File

@@ -21,6 +21,7 @@ import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.annimon.stream.Stream;
@@ -47,6 +48,8 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.thoughtcrime.securesms.database.MmsSmsColumns.Types.GROUP_V2_LEAVE_BITS;
public class MmsSmsDatabase extends Database {
@SuppressWarnings("unused")
@@ -107,11 +110,11 @@ public class MmsSmsDatabase extends Database {
MmsSmsColumns.VIEWED_RECEIPT_COUNT,
MmsSmsColumns.RECEIPT_TIMESTAMP};
private static final String SNIPPET_QUERY = "SELECT " + MmsSmsColumns.ID + ", 0 AS " + TRANSPORT + ", " + SmsDatabase.TYPE + " AS " + MmsSmsColumns.NORMALIZED_TYPE + ", " + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " FROM " + SmsDatabase.TABLE_NAME + " " +
"WHERE " + MmsSmsColumns.THREAD_ID + " = ? AND " + SmsDatabase.TYPE + " NOT IN (" + SmsDatabase.Types.PROFILE_CHANGE_TYPE + ", " + SmsDatabase.Types.GV1_MIGRATION_TYPE + ", " + SmsDatabase.Types.CHANGE_NUMBER_TYPE + ") " +
private static final String SNIPPET_QUERY = "SELECT " + MmsSmsColumns.ID + ", 0 AS " + TRANSPORT + ", " + SmsDatabase.TYPE + " AS " + MmsSmsColumns.NORMALIZED_TYPE + ", " + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " FROM " + SmsDatabase.TABLE_NAME + " " +
"WHERE " + MmsSmsColumns.THREAD_ID + " = ? AND " + SmsDatabase.TYPE + " NOT IN (" + SmsDatabase.Types.PROFILE_CHANGE_TYPE + ", " + SmsDatabase.Types.GV1_MIGRATION_TYPE + ", " + SmsDatabase.Types.CHANGE_NUMBER_TYPE + ") AND " + SmsDatabase.TYPE + " & " + GROUP_V2_LEAVE_BITS + " != " + GROUP_V2_LEAVE_BITS + " " +
"UNION ALL " +
"SELECT " + MmsSmsColumns.ID + ", 1 AS " + TRANSPORT + ", " + MmsDatabase.MESSAGE_BOX + " AS " + MmsSmsColumns.NORMALIZED_TYPE + ", " + MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " FROM " + MmsDatabase.TABLE_NAME + " " +
"WHERE " + MmsSmsColumns.THREAD_ID + " = ? " +
"WHERE " + MmsSmsColumns.THREAD_ID + " = ? AND " + MmsDatabase.MESSAGE_BOX + " & " + GROUP_V2_LEAVE_BITS + " != " + GROUP_V2_LEAVE_BITS + " " +
"ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC " +
"LIMIT 1";
@@ -220,8 +223,7 @@ public class MmsSmsDatabase extends Database {
}
public @NonNull MessageRecord getConversationSnippet(long threadId) throws NoSuchMessageException {
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
try (Cursor cursor = db.rawQuery(SNIPPET_QUERY, SqlUtil.buildArgs(threadId, threadId))) {
try (Cursor cursor = getConversationSnippetCursor(threadId)) {
if (cursor.moveToFirst()) {
boolean isMms = CursorUtil.requireBoolean(cursor, TRANSPORT);
long id = CursorUtil.requireLong(cursor, MmsSmsColumns.ID);
@@ -237,6 +239,12 @@ public class MmsSmsDatabase extends Database {
}
}
@VisibleForTesting
@NonNull Cursor getConversationSnippetCursor(long threadId) {
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
return db.rawQuery(SNIPPET_QUERY, SqlUtil.buildArgs(threadId, threadId));
}
public long getConversationSnippetType(long threadId) throws NoSuchMessageException {
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
try (Cursor cursor = db.rawQuery(SNIPPET_QUERY, SqlUtil.buildArgs(threadId, threadId))) {

View File

@@ -24,6 +24,7 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.annimon.stream.Stream;
import com.google.android.mms.pdu_alt.NotificationInd;
@@ -77,6 +78,8 @@ import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import static org.thoughtcrime.securesms.database.MmsSmsColumns.Types.GROUP_V2_LEAVE_BITS;
/**
* Database for storage of SMS messages.
*
@@ -150,7 +153,8 @@ public class SmsDatabase extends MessageDatabase {
REMOTE_DELETED, NOTIFIED_TIMESTAMP, RECEIPT_TIMESTAMP
};
private static final long IGNORABLE_TYPESMASK_WHEN_COUNTING = Types.END_SESSION_BIT | Types.KEY_EXCHANGE_IDENTITY_UPDATE_BIT | Types.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT;
@VisibleForTesting
static final long IGNORABLE_TYPESMASK_WHEN_COUNTING = Types.END_SESSION_BIT | Types.KEY_EXCHANGE_IDENTITY_UPDATE_BIT | Types.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT;
private static final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache("SmsDelivery");
@@ -271,8 +275,8 @@ public class SmsDatabase extends MessageDatabase {
}
private @NonNull SqlUtil.Query buildMeaningfulMessagesQuery(long threadId) {
String query = THREAD_ID + " = ? AND (NOT " + TYPE + " & ? AND TYPE != ? AND TYPE != ?)";
return SqlUtil.buildQuery(query, threadId, IGNORABLE_TYPESMASK_WHEN_COUNTING, Types.PROFILE_CHANGE_TYPE, Types.CHANGE_NUMBER_TYPE);
String query = THREAD_ID + " = ? AND (NOT " + TYPE + " & ? AND " + TYPE + " != ? AND " + TYPE + "!= ? AND NOT " + TYPE + " & ?)";
return SqlUtil.buildQuery(query, threadId, IGNORABLE_TYPESMASK_WHEN_COUNTING, Types.PROFILE_CHANGE_TYPE, Types.CHANGE_NUMBER_TYPE, GROUP_V2_LEAVE_BITS);
}
@Override
@@ -1088,9 +1092,16 @@ public class SmsDatabase extends MessageDatabase {
type |= Types.SECURE_MESSAGE_BIT;
if (incomingGroupUpdateMessage.isGroupV2()) type |= Types.GROUP_V2_BIT | Types.GROUP_UPDATE_BIT;
else if (incomingGroupUpdateMessage.isUpdate()) type |= Types.GROUP_UPDATE_BIT;
else if (incomingGroupUpdateMessage.isQuit()) type |= Types.GROUP_QUIT_BIT;
if (incomingGroupUpdateMessage.isGroupV2()) {
type |= Types.GROUP_V2_BIT | Types.GROUP_UPDATE_BIT;
if (incomingGroupUpdateMessage.isJustAGroupLeave()) {
type |= Types.GROUP_LEAVE_BIT;
}
} else if (incomingGroupUpdateMessage.isUpdate()) {
type |= Types.GROUP_UPDATE_BIT;
} else if (incomingGroupUpdateMessage.isQuit()) {
type |= Types.GROUP_LEAVE_BIT;
}
} else if (message.isEndSession()) {
type |= Types.SECURE_MESSAGE_BIT;

View File

@@ -1515,7 +1515,8 @@ public class ThreadDatabase extends Database {
private boolean isSilentType(long type) {
return MmsSmsColumns.Types.isProfileChange(type) ||
MmsSmsColumns.Types.isGroupV1MigrationEvent(type) ||
MmsSmsColumns.Types.isChangeNumber(type);
MmsSmsColumns.Types.isChangeNumber(type) ||
MmsSmsColumns.Types.isGroupV2LeaveOnly(type);
}
public Reader readerFor(Cursor cursor) {

View File

@@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.GroupV2UpdateMessageUtil;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
import java.util.Collections;
@@ -70,7 +71,11 @@ public final class OutgoingGroupUpdateMessage extends OutgoingSecureMediaMessage
}
public boolean isV2Group() {
return messageGroupContext.isV2Group();
return GroupV2UpdateMessageUtil.isGroupV2(messageGroupContext);
}
public boolean isJustAGroupLeave() {
return GroupV2UpdateMessageUtil.isJustAGroupLeave(messageGroupContext);
}
public @NonNull MessageGroupContext.GroupV1Properties requireGroupV1Properties() {

View File

@@ -0,0 +1,45 @@
package org.thoughtcrime.securesms.sms;
import androidx.annotation.NonNull;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.thoughtcrime.securesms.mms.MessageGroupContext;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
/**
* Helper util for inspecting GV2 {@link MessageGroupContext} for various message processing.
*/
public final class GroupV2UpdateMessageUtil {
public static boolean isGroupV2(@NonNull MessageGroupContext groupContext) {
return groupContext.isV2Group();
}
public static boolean isUpdate(@NonNull MessageGroupContext groupContext) {
return groupContext.isV2Group();
}
public static boolean isJustAGroupLeave(@NonNull MessageGroupContext groupContext) {
if (isGroupV2(groupContext) && isUpdate(groupContext)) {
DecryptedGroupChange decryptedGroupChange = groupContext.requireGroupV2Properties()
.getChange();
return changeEditorOnlyWasRemoved(decryptedGroupChange) &&
noChangesOtherThanDeletes(decryptedGroupChange);
}
return false;
}
private static boolean changeEditorOnlyWasRemoved(@NonNull DecryptedGroupChange decryptedGroupChange) {
return decryptedGroupChange.getDeleteMembersCount() == 1 &&
decryptedGroupChange.getDeleteMembers(0).equals(decryptedGroupChange.getEditor());
}
private static boolean noChangesOtherThanDeletes(@NonNull DecryptedGroupChange decryptedGroupChange) {
DecryptedGroupChange withoutDeletedMembers = decryptedGroupChange.toBuilder()
.clearDeleteMembers()
.build();
return DecryptedGroupUtil.changeIsEmpty(withoutDeletedMembers);
}
}

View File

@@ -1,11 +1,7 @@
package org.thoughtcrime.securesms.sms;
import androidx.annotation.NonNull;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
import org.thoughtcrime.securesms.mms.MessageGroupContext;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
@@ -32,39 +28,19 @@ public final class IncomingGroupUpdateMessage extends IncomingTextMessage {
}
public boolean isUpdate() {
return groupContext.isV2Group() || groupContext.requireGroupV1Properties().isUpdate();
return GroupV2UpdateMessageUtil.isUpdate(groupContext) || groupContext.requireGroupV1Properties().isUpdate();
}
public boolean isGroupV2() {
return groupContext.isV2Group();
return GroupV2UpdateMessageUtil.isGroupV2(groupContext);
}
public boolean isQuit() {
return !groupContext.isV2Group() && groupContext.requireGroupV1Properties().isQuit();
return !isGroupV2() && groupContext.requireGroupV1Properties().isQuit();
}
@Override
public boolean isJustAGroupLeave() {
if (isGroupV2() && isUpdate()) {
DecryptedGroupChange decryptedGroupChange = groupContext.requireGroupV2Properties()
.getChange();
return changeEditorOnlyWasRemoved(decryptedGroupChange) &&
noChangesOtherThanDeletes(decryptedGroupChange);
}
return false;
}
protected boolean changeEditorOnlyWasRemoved(@NonNull DecryptedGroupChange decryptedGroupChange) {
return decryptedGroupChange.getDeleteMembersCount() == 1 &&
decryptedGroupChange.getDeleteMembers(0).equals(decryptedGroupChange.getEditor());
}
protected boolean noChangesOtherThanDeletes(@NonNull DecryptedGroupChange decryptedGroupChange) {
DecryptedGroupChange withoutDeletedMembers = decryptedGroupChange.toBuilder()
.clearDeleteMembers()
.build();
return DecryptedGroupUtil.changeIsEmpty(withoutDeletedMembers);
return GroupV2UpdateMessageUtil.isJustAGroupLeave(groupContext);
}
}