mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-23 19:26:17 +00:00
Prevent group leave event from bumping conversation.
This commit is contained in:
committed by
Alex Hart
parent
b4465953d8
commit
5e968eb831
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user