mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 09:49:30 +01:00
Add mentions for v2 group chats.
This commit is contained in:
committed by
Greyson Parrelli
parent
0bb9c1d650
commit
b2d4c5d14b
@@ -63,6 +63,7 @@ public class DatabaseFactory {
|
||||
private final KeyValueDatabase keyValueDatabase;
|
||||
private final MegaphoneDatabase megaphoneDatabase;
|
||||
private final RemappedRecordsDatabase remappedRecordsDatabase;
|
||||
private final MentionDatabase mentionDatabase;
|
||||
|
||||
public static DatabaseFactory getInstance(Context context) {
|
||||
synchronized (lock) {
|
||||
@@ -165,6 +166,10 @@ public class DatabaseFactory {
|
||||
return getInstance(context).remappedRecordsDatabase;
|
||||
}
|
||||
|
||||
public static MentionDatabase getMentionDatabase(Context context) {
|
||||
return getInstance(context).mentionDatabase;
|
||||
}
|
||||
|
||||
public static SQLiteDatabase getBackupDatabase(Context context) {
|
||||
return getInstance(context).databaseHelper.getReadableDatabase();
|
||||
}
|
||||
@@ -214,6 +219,7 @@ public class DatabaseFactory {
|
||||
this.keyValueDatabase = new KeyValueDatabase(context, databaseHelper);
|
||||
this.megaphoneDatabase = new MegaphoneDatabase(context, databaseHelper);
|
||||
this.remappedRecordsDatabase = new RemappedRecordsDatabase(context, databaseHelper);
|
||||
this.mentionDatabase = new MentionDatabase(context, databaseHelper);
|
||||
}
|
||||
|
||||
public void onApplicationLevelUpgrade(@NonNull Context context, @NonNull MasterSecret masterSecret,
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
@@ -73,14 +75,11 @@ public class DraftDatabase extends Database {
|
||||
db.delete(TABLE_NAME, null, null);
|
||||
}
|
||||
|
||||
public List<Draft> getDrafts(long threadId) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
List<Draft> results = new LinkedList<>();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = db.query(TABLE_NAME, null, THREAD_ID + " = ?", new String[] {threadId+""}, null, null, null);
|
||||
public Drafts getDrafts(long threadId) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
Drafts results = new Drafts();
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, null, THREAD_ID + " = ?", new String[] {threadId+""}, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String type = cursor.getString(cursor.getColumnIndexOrThrow(DRAFT_TYPE));
|
||||
String value = cursor.getString(cursor.getColumnIndexOrThrow(DRAFT_VALUE));
|
||||
@@ -89,9 +88,6 @@ public class DraftDatabase extends Database {
|
||||
}
|
||||
|
||||
return results;
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +98,7 @@ public class DraftDatabase extends Database {
|
||||
public static final String AUDIO = "audio";
|
||||
public static final String LOCATION = "location";
|
||||
public static final String QUOTE = "quote";
|
||||
public static final String MENTION = "mention";
|
||||
|
||||
private final String type;
|
||||
private final String value;
|
||||
@@ -133,7 +130,7 @@ public class DraftDatabase extends Database {
|
||||
}
|
||||
|
||||
public static class Drafts extends LinkedList<Draft> {
|
||||
private Draft getDraftOfType(String type) {
|
||||
public @Nullable Draft getDraftOfType(String type) {
|
||||
for (Draft draft : this) {
|
||||
if (type.equals(draft.getType())) {
|
||||
return draft;
|
||||
@@ -142,7 +139,7 @@ public class DraftDatabase extends Database {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getSnippet(Context context) {
|
||||
public @NonNull String getSnippet(Context context) {
|
||||
Draft textDraft = getDraftOfType(Draft.TEXT);
|
||||
if (textDraft != null) {
|
||||
return textDraft.getSnippet(context);
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MentionDatabase extends Database {
|
||||
|
||||
private static final String TABLE_NAME = "mention";
|
||||
|
||||
private static final String ID = "_id";
|
||||
private static final String THREAD_ID = "thread_id";
|
||||
private static final String MESSAGE_ID = "message_id";
|
||||
private static final String RECIPIENT_ID = "recipient_id";
|
||||
private static final String RANGE_START = "range_start";
|
||||
private static final String RANGE_LENGTH = "range_length";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
THREAD_ID + " INTEGER, " +
|
||||
MESSAGE_ID + " INTEGER, " +
|
||||
RECIPIENT_ID + " INTEGER, " +
|
||||
RANGE_START + " INTEGER, " +
|
||||
RANGE_LENGTH + " INTEGER)";
|
||||
|
||||
public static final String[] CREATE_INDEXES = new String[] {
|
||||
"CREATE INDEX IF NOT EXISTS mention_message_id_index ON " + TABLE_NAME + " (" + MESSAGE_ID + ");",
|
||||
"CREATE INDEX IF NOT EXISTS mention_recipient_id_thread_id_index ON " + TABLE_NAME + " (" + RECIPIENT_ID + ", " + THREAD_ID + ");"
|
||||
};
|
||||
|
||||
public MentionDatabase(@NonNull Context context, @NonNull SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public void insert(long threadId, long messageId, @NonNull Collection<Mention> mentions) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
for (Mention mention : mentions) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(THREAD_ID, threadId);
|
||||
values.put(MESSAGE_ID, messageId);
|
||||
values.put(RECIPIENT_ID, mention.getRecipientId().toLong());
|
||||
values.put(RANGE_START, mention.getStart());
|
||||
values.put(RANGE_LENGTH, mention.getLength());
|
||||
db.insert(TABLE_NAME, null, values);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull List<Mention> getMentionsForMessage(long messageId) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
List<Mention> mentions = new LinkedList<>();
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, null, MESSAGE_ID + " = ?", SqlUtil.buildArgs(messageId), null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
mentions.add(new Mention(RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID)),
|
||||
CursorUtil.requireInt(cursor, RANGE_START),
|
||||
CursorUtil.requireInt(cursor, RANGE_LENGTH)));
|
||||
}
|
||||
}
|
||||
|
||||
return mentions;
|
||||
}
|
||||
|
||||
public @NonNull Map<Long, List<Mention>> getMentionsForMessages(@NonNull Collection<Long> messageIds) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
Map<Long, List<Mention>> mentions = new HashMap<>();
|
||||
|
||||
String ids = TextUtils.join(",", messageIds);
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, null, MESSAGE_ID + " IN (" + ids + ")", null, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
long messageId = CursorUtil.requireLong(cursor, MESSAGE_ID);
|
||||
List<Mention> messageMentions = mentions.get(messageId);
|
||||
|
||||
if (messageMentions == null) {
|
||||
messageMentions = new LinkedList<>();
|
||||
mentions.put(messageId, messageMentions);
|
||||
}
|
||||
|
||||
messageMentions.add(new Mention(RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID)),
|
||||
CursorUtil.requireInt(cursor, RANGE_START),
|
||||
CursorUtil.requireInt(cursor, RANGE_LENGTH)));
|
||||
}
|
||||
}
|
||||
|
||||
return mentions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.annimon.stream.function.Function;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.MentionSetting;
|
||||
import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class MentionUtil {
|
||||
|
||||
public static final char MENTION_STARTER = '@';
|
||||
static final String MENTION_PLACEHOLDER = "\uFFFC";
|
||||
|
||||
private MentionUtil() { }
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull CharSequence updateBodyWithDisplayNames(@NonNull Context context, @NonNull MessageRecord messageRecord) {
|
||||
return updateBodyWithDisplayNames(context, messageRecord, messageRecord.getDisplayBody(context));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull CharSequence updateBodyWithDisplayNames(@NonNull Context context, @NonNull MessageRecord messageRecord, @NonNull CharSequence body) {
|
||||
if (messageRecord.isMms()) {
|
||||
List<Mention> mentions = DatabaseFactory.getMentionDatabase(context).getMentionsForMessage(messageRecord.getId());
|
||||
CharSequence updated = updateBodyAndMentionsWithDisplayNames(context, body, mentions).getBody();
|
||||
if (updated != null) {
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull UpdatedBodyAndMentions updateBodyAndMentionsWithDisplayNames(@NonNull Context context, @NonNull MessageRecord messageRecord, @NonNull List<Mention> mentions) {
|
||||
return updateBodyAndMentionsWithDisplayNames(context, messageRecord.getDisplayBody(context), mentions);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull UpdatedBodyAndMentions updateBodyAndMentionsWithDisplayNames(@NonNull Context context, @NonNull CharSequence body, @NonNull List<Mention> mentions) {
|
||||
return update(body, mentions, m -> MENTION_STARTER + Recipient.resolved(m.getRecipientId()).getDisplayName(context));
|
||||
}
|
||||
|
||||
public static @NonNull UpdatedBodyAndMentions updateBodyAndMentionsWithPlaceholders(@Nullable CharSequence body, @NonNull List<Mention> mentions) {
|
||||
return update(body, mentions, m -> MENTION_PLACEHOLDER);
|
||||
}
|
||||
|
||||
private static @NonNull UpdatedBodyAndMentions update(@Nullable CharSequence body, @NonNull List<Mention> mentions, @NonNull Function<Mention, CharSequence> replacementTextGenerator) {
|
||||
if (body == null || mentions.isEmpty()) {
|
||||
return new UpdatedBodyAndMentions(body, mentions);
|
||||
}
|
||||
|
||||
SpannableStringBuilder updatedBody = new SpannableStringBuilder();
|
||||
List<Mention> updatedMentions = new ArrayList<>();
|
||||
|
||||
Collections.sort(mentions);
|
||||
|
||||
int bodyIndex = 0;
|
||||
|
||||
for (Mention mention : mentions) {
|
||||
updatedBody.append(body.subSequence(bodyIndex, mention.getStart()));
|
||||
CharSequence replaceWith = replacementTextGenerator.apply(mention);
|
||||
Mention updatedMention = new Mention(mention.getRecipientId(), updatedBody.length(), replaceWith.length());
|
||||
|
||||
updatedBody.append(replaceWith);
|
||||
updatedMentions.add(updatedMention);
|
||||
|
||||
bodyIndex = mention.getStart() + mention.getLength();
|
||||
}
|
||||
|
||||
if (bodyIndex < body.length()) {
|
||||
updatedBody.append(body.subSequence(bodyIndex, body.length()));
|
||||
}
|
||||
|
||||
return new UpdatedBodyAndMentions(updatedBody.toString(), updatedMentions);
|
||||
}
|
||||
|
||||
public static @Nullable BodyRangeList mentionsToBodyRangeList(@Nullable List<Mention> mentions) {
|
||||
if (mentions == null || mentions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BodyRangeList.Builder builder = BodyRangeList.newBuilder();
|
||||
|
||||
for (Mention mention : mentions) {
|
||||
String uuid = Recipient.resolved(mention.getRecipientId()).requireUuid().toString();
|
||||
builder.addRanges(BodyRangeList.BodyRange.newBuilder()
|
||||
.setMentionUuid(uuid)
|
||||
.setStart(mention.getStart())
|
||||
.setLength(mention.getLength()));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static @NonNull List<Mention> bodyRangeListToMentions(@NonNull Context context, @Nullable byte[] data) {
|
||||
if (data != null) {
|
||||
try {
|
||||
return Stream.of(BodyRangeList.parseFrom(data).getRangesList())
|
||||
.filter(bodyRange -> bodyRange.getAssociatedValueCase() == BodyRangeList.BodyRange.AssociatedValueCase.MENTIONUUID)
|
||||
.map(mention -> {
|
||||
RecipientId id = Recipient.externalPush(context, UuidUtil.parseOrThrow(mention.getMentionUuid()), null, false).getId();
|
||||
return new Mention(id, mention.getStart(), mention.getLength());
|
||||
})
|
||||
.toList();
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull String getMentionSettingDisplayValue(@NonNull Context context, @NonNull MentionSetting mentionSetting) {
|
||||
switch (mentionSetting) {
|
||||
case GLOBAL:
|
||||
return context.getString(SignalStore.notificationSettings().isMentionNotifiesMeEnabled() ? R.string.GroupMentionSettingDialog_default_notify_me
|
||||
: R.string.GroupMentionSettingDialog_default_dont_notify_me);
|
||||
case ALWAYS_NOTIFY:
|
||||
return context.getString(R.string.GroupMentionSettingDialog_always_notify_me);
|
||||
case DO_NOT_NOTIFY:
|
||||
return context.getString(R.string.GroupMentionSettingDialog_dont_notify_me);
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown mention setting: " + mentionSetting);
|
||||
}
|
||||
|
||||
public static class UpdatedBodyAndMentions {
|
||||
@Nullable private final CharSequence body;
|
||||
@NonNull private final List<Mention> mentions;
|
||||
|
||||
public UpdatedBodyAndMentions(@Nullable CharSequence body, @NonNull List<Mention> mentions) {
|
||||
this.body = body;
|
||||
this.mentions = mentions;
|
||||
}
|
||||
|
||||
public @Nullable CharSequence getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public @NonNull List<Mention> getMentions() {
|
||||
return mentions;
|
||||
}
|
||||
|
||||
@Nullable String getBodyAsString() {
|
||||
return body != null ? body.toString() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,10 +47,12 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailureList;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.Quote;
|
||||
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
@@ -68,6 +70,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.revealable.ViewOnceExpirationInfo;
|
||||
import org.thoughtcrime.securesms.revealable.ViewOnceUtil;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -109,9 +112,11 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
static final String QUOTE_BODY = "quote_body";
|
||||
static final String QUOTE_ATTACHMENT = "quote_attachment";
|
||||
static final String QUOTE_MISSING = "quote_missing";
|
||||
static final String QUOTE_MENTIONS = "quote_mentions";
|
||||
|
||||
static final String SHARED_CONTACTS = "shared_contacts";
|
||||
static final String LINK_PREVIEWS = "previews";
|
||||
static final String MENTIONS_SELF = "mentions_self";
|
||||
|
||||
public static final String VIEW_ONCE = "reveal_duration";
|
||||
|
||||
@@ -163,6 +168,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
QUOTE_BODY + " TEXT, " +
|
||||
QUOTE_ATTACHMENT + " INTEGER DEFAULT -1, " +
|
||||
QUOTE_MISSING + " INTEGER DEFAULT 0, " +
|
||||
QUOTE_MENTIONS + " BLOB DEFAULT NULL," +
|
||||
SHARED_CONTACTS + " TEXT, " +
|
||||
UNIDENTIFIED + " INTEGER DEFAULT 0, " +
|
||||
LINK_PREVIEWS + " TEXT, " +
|
||||
@@ -170,7 +176,8 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
REACTIONS + " BLOB DEFAULT NULL, " +
|
||||
REACTIONS_UNREAD + " INTEGER DEFAULT 0, " +
|
||||
REACTIONS_LAST_SEEN + " INTEGER DEFAULT -1, " +
|
||||
REMOTE_DELETED + " INTEGER DEFAULT 0);";
|
||||
REMOTE_DELETED + " INTEGER DEFAULT 0, " +
|
||||
MENTIONS_SELF + " INTEGER DEFAULT 0);";
|
||||
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
|
||||
@@ -193,9 +200,9 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
MESSAGE_SIZE, STATUS, TRANSACTION_ID,
|
||||
BODY, PART_COUNT, RECIPIENT_ID, ADDRESS_DEVICE_ID,
|
||||
DELIVERY_RECEIPT_COUNT, READ_RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID,
|
||||
EXPIRES_IN, EXPIRE_STARTED, NOTIFIED, QUOTE_ID, QUOTE_AUTHOR, QUOTE_BODY, QUOTE_ATTACHMENT, QUOTE_MISSING,
|
||||
EXPIRES_IN, EXPIRE_STARTED, NOTIFIED, QUOTE_ID, QUOTE_AUTHOR, QUOTE_BODY, QUOTE_ATTACHMENT, QUOTE_MISSING, QUOTE_MENTIONS,
|
||||
SHARED_CONTACTS, LINK_PREVIEWS, UNIDENTIFIED, VIEW_ONCE, REACTIONS, REACTIONS_UNREAD, REACTIONS_LAST_SEEN,
|
||||
REMOTE_DELETED,
|
||||
REMOTE_DELETED, MENTIONS_SELF,
|
||||
"json_group_array(json_object(" +
|
||||
"'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " +
|
||||
"'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " +
|
||||
@@ -805,6 +812,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
throws MmsException, NoSuchMessageException
|
||||
{
|
||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
MentionDatabase mentionDatabase = DatabaseFactory.getMentionDatabase(context);
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
@@ -812,6 +820,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
List<DatabaseAttachment> associatedAttachments = attachmentDatabase.getAttachmentsForMessage(messageId);
|
||||
List<Mention> mentions = mentionDatabase.getMentionsForMessage(messageId);
|
||||
|
||||
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
|
||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
|
||||
@@ -830,6 +839,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
String quoteText = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_BODY));
|
||||
boolean quoteMissing = cursor.getInt(cursor.getColumnIndexOrThrow(QUOTE_MISSING)) == 1;
|
||||
List<Attachment> quoteAttachments = Stream.of(associatedAttachments).filter(Attachment::isQuote).map(a -> (Attachment)a).toList();
|
||||
List<Mention> quoteMentions = parseQuoteMentions(cursor);
|
||||
List<Contact> contacts = getSharedContacts(cursor, associatedAttachments);
|
||||
Set<Attachment> contactAttachments = new HashSet<>(Stream.of(contacts).map(Contact::getAvatarAttachment).filter(a -> a != null).toList());
|
||||
List<LinkPreview> previews = getLinkPreviews(cursor, associatedAttachments);
|
||||
@@ -846,7 +856,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
QuoteModel quote = null;
|
||||
|
||||
if (quoteId > 0 && quoteAuthor > 0 && (!TextUtils.isEmpty(quoteText) || !quoteAttachments.isEmpty())) {
|
||||
quote = new QuoteModel(quoteId, RecipientId.from(quoteAuthor), quoteText, quoteMissing, quoteAttachments);
|
||||
quote = new QuoteModel(quoteId, RecipientId.from(quoteAuthor), quoteText, quoteMissing, quoteAttachments, quoteMentions);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(mismatchDocument)) {
|
||||
@@ -866,12 +876,12 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
|
||||
if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) {
|
||||
return new OutgoingGroupUpdateMessage(recipient, new MessageGroupContext(body, Types.isGroupV2(outboxType)), attachments, timestamp, 0, false, quote, contacts, previews);
|
||||
return new OutgoingGroupUpdateMessage(recipient, new MessageGroupContext(body, Types.isGroupV2(outboxType)), attachments, timestamp, 0, false, quote, contacts, previews, mentions);
|
||||
} else if (Types.isExpirationTimerUpdate(outboxType)) {
|
||||
return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn);
|
||||
}
|
||||
|
||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, viewOnce, distributionType, quote, contacts, previews, networkFailures, mismatches);
|
||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, viewOnce, distributionType, quote, contacts, previews, mentions, networkFailures, mismatches);
|
||||
|
||||
if (Types.isSecureType(outboxType)) {
|
||||
return new OutgoingSecureMediaMessage(message);
|
||||
@@ -1000,10 +1010,15 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
|
||||
if (retrieved.getQuote() != null) {
|
||||
contentValues.put(QUOTE_ID, retrieved.getQuote().getId());
|
||||
contentValues.put(QUOTE_BODY, retrieved.getQuote().getText());
|
||||
contentValues.put(QUOTE_BODY, retrieved.getQuote().getText().toString());
|
||||
contentValues.put(QUOTE_AUTHOR, retrieved.getQuote().getAuthor().serialize());
|
||||
contentValues.put(QUOTE_MISSING, retrieved.getQuote().isOriginalMissing() ? 1 : 0);
|
||||
|
||||
BodyRangeList mentionsList = MentionUtil.mentionsToBodyRangeList(retrieved.getQuote().getMentions());
|
||||
if (mentionsList != null) {
|
||||
contentValues.put(QUOTE_MENTIONS, mentionsList.toByteArray());
|
||||
}
|
||||
|
||||
quoteAttachments = retrieved.getQuote().getAttachments();
|
||||
}
|
||||
|
||||
@@ -1012,7 +1027,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
long messageId = insertMediaMessage(retrieved.getBody(), retrieved.getAttachments(), quoteAttachments, retrieved.getSharedContacts(), retrieved.getLinkPreviews(), contentValues, null);
|
||||
long messageId = insertMediaMessage(threadId, retrieved.getBody(), retrieved.getAttachments(), quoteAttachments, retrieved.getSharedContacts(), retrieved.getLinkPreviews(), retrieved.getMentions(), contentValues, null);
|
||||
|
||||
if (!Types.isExpirationTimerUpdate(mailbox)) {
|
||||
DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
|
||||
@@ -1158,15 +1173,23 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
List<Attachment> quoteAttachments = new LinkedList<>();
|
||||
|
||||
if (message.getOutgoingQuote() != null) {
|
||||
MentionUtil.UpdatedBodyAndMentions updated = MentionUtil.updateBodyAndMentionsWithPlaceholders(message.getOutgoingQuote().getText(), message.getOutgoingQuote().getMentions());
|
||||
|
||||
contentValues.put(QUOTE_ID, message.getOutgoingQuote().getId());
|
||||
contentValues.put(QUOTE_AUTHOR, message.getOutgoingQuote().getAuthor().serialize());
|
||||
contentValues.put(QUOTE_BODY, message.getOutgoingQuote().getText());
|
||||
contentValues.put(QUOTE_BODY, updated.getBodyAsString());
|
||||
contentValues.put(QUOTE_MISSING, message.getOutgoingQuote().isOriginalMissing() ? 1 : 0);
|
||||
|
||||
BodyRangeList mentionsList = MentionUtil.mentionsToBodyRangeList(updated.getMentions());
|
||||
if (mentionsList != null) {
|
||||
contentValues.put(QUOTE_MENTIONS, mentionsList.toByteArray());
|
||||
}
|
||||
|
||||
quoteAttachments.addAll(message.getOutgoingQuote().getAttachments());
|
||||
}
|
||||
|
||||
long messageId = insertMediaMessage(message.getBody(), message.getAttachments(), quoteAttachments, message.getSharedContacts(), message.getLinkPreviews(), contentValues, insertListener);
|
||||
MentionUtil.UpdatedBodyAndMentions updatedBodyAndMentions = MentionUtil.updateBodyAndMentionsWithPlaceholders(message.getBody(), message.getMentions());
|
||||
long messageId = insertMediaMessage(threadId, updatedBodyAndMentions.getBodyAsString(), message.getAttachments(), quoteAttachments, message.getSharedContacts(), message.getLinkPreviews(), updatedBodyAndMentions.getMentions(), contentValues, insertListener);
|
||||
|
||||
if (message.getRecipient().isGroup()) {
|
||||
OutgoingGroupUpdateMessage outgoingGroupUpdateMessage = (message instanceof OutgoingGroupUpdateMessage) ? (OutgoingGroupUpdateMessage) message : null;
|
||||
@@ -1197,17 +1220,22 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
private long insertMediaMessage(@Nullable String body,
|
||||
private long insertMediaMessage(long threadId,
|
||||
@Nullable String body,
|
||||
@NonNull List<Attachment> attachments,
|
||||
@NonNull List<Attachment> quoteAttachments,
|
||||
@NonNull List<Contact> sharedContacts,
|
||||
@NonNull List<LinkPreview> linkPreviews,
|
||||
@NonNull List<Mention> mentions,
|
||||
@NonNull ContentValues contentValues,
|
||||
@Nullable SmsDatabase.InsertListener insertListener)
|
||||
throws MmsException
|
||||
{
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
AttachmentDatabase partsDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
AttachmentDatabase partsDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
MentionDatabase mentionDatabase = DatabaseFactory.getMentionDatabase(context);
|
||||
|
||||
boolean mentionsSelf = Stream.of(mentions).filter(m -> Recipient.resolved(m.getRecipientId()).isLocalNumber()).findFirst().isPresent();
|
||||
|
||||
List<Attachment> allAttachments = new LinkedList<>();
|
||||
List<Attachment> contactAttachments = Stream.of(sharedContacts).map(Contact::getAvatarAttachment).filter(a -> a != null).toList();
|
||||
@@ -1219,11 +1247,14 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
|
||||
contentValues.put(BODY, body);
|
||||
contentValues.put(PART_COUNT, allAttachments.size());
|
||||
contentValues.put(MENTIONS_SELF, mentionsSelf ? 1 : 0);
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
long messageId = db.insert(TABLE_NAME, null, contentValues);
|
||||
|
||||
mentionDatabase.insert(threadId, messageId, mentions);
|
||||
|
||||
Map<Attachment, AttachmentId> insertedAttachments = partsDatabase.insertAttachmentsForMessage(messageId, allAttachments, quoteAttachments);
|
||||
String serializedContacts = getSerializedSharedContacts(insertedAttachments, sharedContacts);
|
||||
String serializedPreviews = getSerializedLinkPreviews(insertedAttachments, linkPreviews);
|
||||
@@ -1468,6 +1499,12 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull List<Mention> parseQuoteMentions(Cursor cursor) {
|
||||
byte[] raw = cursor.getBlob(cursor.getColumnIndexOrThrow(QUOTE_MENTIONS));
|
||||
|
||||
return MentionUtil.bodyRangeListToMentions(context, raw);
|
||||
}
|
||||
|
||||
public void beginTransaction() {
|
||||
databaseHelper.getWritableDatabase().beginTransaction();
|
||||
}
|
||||
@@ -1542,6 +1579,16 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
public MessageRecord getCurrent() {
|
||||
SlideDeck slideDeck = new SlideDeck(context, message.getAttachments());
|
||||
|
||||
CharSequence quoteText = message.getOutgoingQuote() != null ? message.getOutgoingQuote().getText() : null;
|
||||
List<Mention> quoteMentions = message.getOutgoingQuote() != null ? message.getOutgoingQuote().getMentions() : Collections.emptyList();
|
||||
|
||||
if (quoteText != null && !quoteMentions.isEmpty()) {
|
||||
MentionUtil.UpdatedBodyAndMentions updated = MentionUtil.updateBodyAndMentionsWithDisplayNames(context, quoteText, quoteMentions);
|
||||
|
||||
quoteText = updated.getBody();
|
||||
quoteMentions = updated.getMentions();
|
||||
}
|
||||
|
||||
return new MediaMmsMessageRecord(id,
|
||||
message.getRecipient(),
|
||||
message.getRecipient(),
|
||||
@@ -1564,14 +1611,16 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
message.getOutgoingQuote() != null ?
|
||||
new Quote(message.getOutgoingQuote().getId(),
|
||||
message.getOutgoingQuote().getAuthor(),
|
||||
message.getOutgoingQuote().getText(),
|
||||
quoteText,
|
||||
message.getOutgoingQuote().isOriginalMissing(),
|
||||
new SlideDeck(context, message.getOutgoingQuote().getAttachments())) :
|
||||
new SlideDeck(context, message.getOutgoingQuote().getAttachments()),
|
||||
quoteMentions) :
|
||||
null,
|
||||
message.getSharedContacts(),
|
||||
message.getLinkPreviews(),
|
||||
false,
|
||||
Collections.emptyList(),
|
||||
false,
|
||||
false);
|
||||
}
|
||||
}
|
||||
@@ -1665,6 +1714,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
boolean isViewOnce = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.VIEW_ONCE)) == 1;
|
||||
boolean remoteDelete = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.REMOTE_DELETED)) == 1;
|
||||
List<ReactionRecord> reactions = parseReactions(cursor);
|
||||
boolean mentionsSelf = CursorUtil.requireBoolean(cursor, MENTIONS_SELF);
|
||||
|
||||
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||
readReceiptCount = 0;
|
||||
@@ -1686,7 +1736,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
threadId, body, slideDeck, partCount, box, mismatches,
|
||||
networkFailures, subscriptionId, expiresIn, expireStarted,
|
||||
isViewOnce, readReceiptCount, quote, contacts, previews, unidentified, reactions,
|
||||
remoteDelete);
|
||||
remoteDelete, mentionsSelf);
|
||||
}
|
||||
|
||||
private List<IdentityKeyMismatch> getMismatchedIdentities(String document) {
|
||||
@@ -1724,14 +1774,22 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
private @Nullable Quote getQuote(@NonNull Cursor cursor) {
|
||||
long quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_ID));
|
||||
long quoteAuthor = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_AUTHOR));
|
||||
String quoteText = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_BODY));
|
||||
CharSequence quoteText = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_BODY));
|
||||
boolean quoteMissing = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.QUOTE_MISSING)) == 1;
|
||||
List<Mention> quoteMentions = parseQuoteMentions(cursor);
|
||||
List<DatabaseAttachment> attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachment(cursor);
|
||||
List<? extends Attachment> quoteAttachments = Stream.of(attachments).filter(Attachment::isQuote).toList();
|
||||
SlideDeck quoteDeck = new SlideDeck(context, quoteAttachments);
|
||||
|
||||
if (quoteId > 0 && quoteAuthor > 0) {
|
||||
return new Quote(quoteId, RecipientId.from(quoteAuthor), quoteText, quoteMissing, quoteDeck);
|
||||
if (quoteText != null && !quoteMentions.isEmpty()) {
|
||||
MentionUtil.UpdatedBodyAndMentions updated = MentionUtil.updateBodyAndMentionsWithDisplayNames(context, quoteText, quoteMentions);
|
||||
|
||||
quoteText = updated.getBody();
|
||||
quoteMentions = updated.getMentions();
|
||||
}
|
||||
|
||||
return new Quote(quoteId, RecipientId.from(quoteAuthor), quoteText, quoteMissing, quoteDeck, quoteMentions);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsDatabase.QUOTE_BODY,
|
||||
MmsDatabase.QUOTE_MISSING,
|
||||
MmsDatabase.QUOTE_ATTACHMENT,
|
||||
MmsDatabase.QUOTE_MENTIONS,
|
||||
MmsDatabase.SHARED_CONTACTS,
|
||||
MmsDatabase.LINK_PREVIEWS,
|
||||
MmsDatabase.VIEW_ONCE,
|
||||
@@ -89,7 +90,8 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsSmsColumns.REACTIONS,
|
||||
MmsSmsColumns.REACTIONS_UNREAD,
|
||||
MmsSmsColumns.REACTIONS_LAST_SEEN,
|
||||
MmsSmsColumns.REMOTE_DELETED};
|
||||
MmsSmsColumns.REMOTE_DELETED,
|
||||
MmsDatabase.MENTIONS_SELF};
|
||||
|
||||
public MmsSmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
@@ -408,6 +410,7 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsDatabase.QUOTE_BODY,
|
||||
MmsDatabase.QUOTE_MISSING,
|
||||
MmsDatabase.QUOTE_ATTACHMENT,
|
||||
MmsDatabase.QUOTE_MENTIONS,
|
||||
MmsDatabase.SHARED_CONTACTS,
|
||||
MmsDatabase.LINK_PREVIEWS,
|
||||
MmsDatabase.VIEW_ONCE,
|
||||
@@ -415,7 +418,8 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsSmsColumns.REACTIONS_UNREAD,
|
||||
MmsSmsColumns.REACTIONS_LAST_SEEN,
|
||||
MmsSmsColumns.DATE_SERVER,
|
||||
MmsSmsColumns.REMOTE_DELETED };
|
||||
MmsSmsColumns.REMOTE_DELETED,
|
||||
MmsDatabase.MENTIONS_SELF };
|
||||
|
||||
String[] smsProjection = {SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
@@ -440,6 +444,7 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsDatabase.QUOTE_BODY,
|
||||
MmsDatabase.QUOTE_MISSING,
|
||||
MmsDatabase.QUOTE_ATTACHMENT,
|
||||
MmsDatabase.QUOTE_MENTIONS,
|
||||
MmsDatabase.SHARED_CONTACTS,
|
||||
MmsDatabase.LINK_PREVIEWS,
|
||||
MmsDatabase.VIEW_ONCE,
|
||||
@@ -447,7 +452,8 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsSmsColumns.REACTIONS_UNREAD,
|
||||
MmsSmsColumns.REACTIONS_LAST_SEEN,
|
||||
MmsSmsColumns.DATE_SERVER,
|
||||
MmsSmsColumns.REMOTE_DELETED };
|
||||
MmsSmsColumns.REMOTE_DELETED,
|
||||
MmsDatabase.MENTIONS_SELF };
|
||||
|
||||
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
|
||||
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
|
||||
@@ -493,6 +499,7 @@ public class MmsSmsDatabase extends Database {
|
||||
mmsColumnsPresent.add(MmsDatabase.QUOTE_BODY);
|
||||
mmsColumnsPresent.add(MmsDatabase.QUOTE_MISSING);
|
||||
mmsColumnsPresent.add(MmsDatabase.QUOTE_ATTACHMENT);
|
||||
mmsColumnsPresent.add(MmsDatabase.QUOTE_MENTIONS);
|
||||
mmsColumnsPresent.add(MmsDatabase.SHARED_CONTACTS);
|
||||
mmsColumnsPresent.add(MmsDatabase.LINK_PREVIEWS);
|
||||
mmsColumnsPresent.add(MmsDatabase.VIEW_ONCE);
|
||||
@@ -500,6 +507,7 @@ public class MmsSmsDatabase extends Database {
|
||||
mmsColumnsPresent.add(MmsDatabase.REACTIONS_UNREAD);
|
||||
mmsColumnsPresent.add(MmsDatabase.REACTIONS_LAST_SEEN);
|
||||
mmsColumnsPresent.add(MmsDatabase.REMOTE_DELETED);
|
||||
mmsColumnsPresent.add(MmsDatabase.MENTIONS_SELF);
|
||||
|
||||
Set<String> smsColumnsPresent = new HashSet<>();
|
||||
smsColumnsPresent.add(MmsSmsColumns.ID);
|
||||
|
||||
@@ -119,13 +119,13 @@ public class RecipientDatabase extends Database {
|
||||
private static final String PROFILE_GIVEN_NAME = "signal_profile_name";
|
||||
private static final String PROFILE_FAMILY_NAME = "profile_family_name";
|
||||
private static final String PROFILE_JOINED_NAME = "profile_joined_name";
|
||||
private static final String MENTION_SETTING = "mention_setting";
|
||||
|
||||
public static final String SEARCH_PROFILE_NAME = "search_signal_profile";
|
||||
private static final String SORT_NAME = "sort_name";
|
||||
private static final String IDENTITY_STATUS = "identity_status";
|
||||
private static final String IDENTITY_KEY = "identity_key";
|
||||
|
||||
|
||||
private static final String[] RECIPIENT_PROJECTION = new String[] {
|
||||
UUID, USERNAME, PHONE, EMAIL, GROUP_ID, GROUP_TYPE,
|
||||
BLOCKED, MESSAGE_RINGTONE, CALL_RINGTONE, MESSAGE_VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, MESSAGE_EXPIRATION_TIME, REGISTERED,
|
||||
@@ -136,7 +136,8 @@ public class RecipientDatabase extends Database {
|
||||
UNIDENTIFIED_ACCESS_MODE,
|
||||
FORCE_SMS_SELECTION,
|
||||
UUID_CAPABILITY, GROUPS_V2_CAPABILITY,
|
||||
STORAGE_SERVICE_ID, DIRTY
|
||||
STORAGE_SERVICE_ID, DIRTY,
|
||||
MENTION_SETTING
|
||||
};
|
||||
|
||||
private static final String[] ID_PROJECTION = new String[]{ID};
|
||||
@@ -146,6 +147,8 @@ public class RecipientDatabase extends Database {
|
||||
.map(columnName -> TABLE_NAME + "." + columnName)
|
||||
.toList().toArray(new String[0]);
|
||||
|
||||
private static final String[] MENTION_SEARCH_PROJECTION = new String[]{ID, removeWhitespace("COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ", " + nullIfEmpty(PHONE) + ")") + " AS " + SORT_NAME};
|
||||
|
||||
private static final String[] RECIPIENT_FULL_PROJECTION = ArrayUtils.concat(
|
||||
new String[] { TABLE_NAME + "." + ID },
|
||||
TYPED_RECIPIENT_PROJECTION,
|
||||
@@ -275,6 +278,24 @@ public class RecipientDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public enum MentionSetting {
|
||||
GLOBAL(0), ALWAYS_NOTIFY(1), DO_NOT_NOTIFY(2);
|
||||
|
||||
private final int id;
|
||||
|
||||
MentionSetting(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static MentionSetting fromId(int id) {
|
||||
return values()[id];
|
||||
}
|
||||
}
|
||||
|
||||
public static final String CREATE_TABLE =
|
||||
"CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
UUID + " TEXT UNIQUE DEFAULT NULL, " +
|
||||
@@ -314,7 +335,8 @@ public class RecipientDatabase extends Database {
|
||||
UUID_CAPABILITY + " INTEGER DEFAULT " + Recipient.Capability.UNKNOWN.serialize() + ", " +
|
||||
GROUPS_V2_CAPABILITY + " INTEGER DEFAULT " + Recipient.Capability.UNKNOWN.serialize() + ", " +
|
||||
STORAGE_SERVICE_ID + " TEXT UNIQUE DEFAULT NULL, " +
|
||||
DIRTY + " INTEGER DEFAULT " + DirtyState.CLEAN.getId() + ");";
|
||||
DIRTY + " INTEGER DEFAULT " + DirtyState.CLEAN.getId() + ", " +
|
||||
MENTION_SETTING + " INTEGER DEFAULT " + MentionSetting.GLOBAL.getId() + ");";
|
||||
|
||||
private static final String INSIGHTS_INVITEE_LIST = "SELECT " + TABLE_NAME + "." + ID +
|
||||
" FROM " + TABLE_NAME +
|
||||
@@ -1078,6 +1100,7 @@ public class RecipientDatabase extends Database {
|
||||
int uuidCapabilityValue = CursorUtil.requireInt(cursor, UUID_CAPABILITY);
|
||||
int groupsV2CapabilityValue = CursorUtil.requireInt(cursor, GROUPS_V2_CAPABILITY);
|
||||
String storageKeyRaw = CursorUtil.requireString(cursor, STORAGE_SERVICE_ID);
|
||||
int mentionSettingId = CursorUtil.requireInt(cursor, MENTION_SETTING);
|
||||
|
||||
Optional<String> identityKeyRaw = CursorUtil.getString(cursor, IDENTITY_KEY);
|
||||
Optional<Integer> identityStatusRaw = CursorUtil.getInt(cursor, IDENTITY_STATUS);
|
||||
@@ -1145,7 +1168,7 @@ public class RecipientDatabase extends Database {
|
||||
Recipient.Capability.deserialize(uuidCapabilityValue),
|
||||
Recipient.Capability.deserialize(groupsV2CapabilityValue),
|
||||
InsightsBannerTier.fromId(insightsBannerTier),
|
||||
storageKey, identityKey, identityStatus);
|
||||
storageKey, identityKey, identityStatus, MentionSetting.fromId(mentionSettingId));
|
||||
}
|
||||
|
||||
public BulkOperationsHandle beginBulkSystemContactUpdate() {
|
||||
@@ -1299,6 +1322,14 @@ public class RecipientDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public void setMentionSetting(@NonNull RecipientId id, @NonNull MentionSetting mentionSetting) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MENTION_SETTING, mentionSetting.getId());
|
||||
if (update(id, values)) {
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the profile key.
|
||||
* <p>
|
||||
@@ -1902,6 +1933,29 @@ public class RecipientDatabase extends Database {
|
||||
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, null);
|
||||
}
|
||||
|
||||
public @NonNull List<Recipient> queryRecipientsForMentions(@NonNull String query, @NonNull List<RecipientId> recipientIds) {
|
||||
if (TextUtils.isEmpty(query) || recipientIds.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
query = buildCaseInsensitiveGlobPattern(query);
|
||||
|
||||
String ids = TextUtils.join(",", Stream.of(recipientIds).map(RecipientId::serialize).toList());
|
||||
|
||||
String selection = BLOCKED + " = 0 AND " +
|
||||
ID + " IN (" + ids + ") AND " +
|
||||
SORT_NAME + " GLOB ?";
|
||||
|
||||
List<Recipient> recipients = new ArrayList<>();
|
||||
try (RecipientDatabase.RecipientReader reader = new RecipientReader(databaseHelper.getReadableDatabase().query(TABLE_NAME, MENTION_SEARCH_PROJECTION, selection, SqlUtil.buildArgs(query), null, null, SORT_NAME))) {
|
||||
Recipient recipient;
|
||||
while ((recipient = reader.getNext()) != null) {
|
||||
recipients.add(recipient);
|
||||
}
|
||||
}
|
||||
return recipients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a case-insensitive GLOB pattern for fuzzy text queries. Works with all unicode
|
||||
* characters.
|
||||
@@ -2384,6 +2438,10 @@ public class RecipientDatabase extends Database {
|
||||
return "NULLIF(" + column + ", '')";
|
||||
}
|
||||
|
||||
private static @NonNull String removeWhitespace(@NonNull String column) {
|
||||
return "REPLACE(" + column + ", ' ', '')";
|
||||
}
|
||||
|
||||
public interface ColorUpdater {
|
||||
MaterialColor update(@NonNull String name, @Nullable String color);
|
||||
}
|
||||
@@ -2427,6 +2485,7 @@ public class RecipientDatabase extends Database {
|
||||
private final byte[] storageId;
|
||||
private final byte[] identityKey;
|
||||
private final IdentityDatabase.VerifiedStatus identityStatus;
|
||||
private final MentionSetting mentionSetting;
|
||||
|
||||
RecipientSettings(@NonNull RecipientId id,
|
||||
@Nullable UUID uuid,
|
||||
@@ -2465,7 +2524,8 @@ public class RecipientDatabase extends Database {
|
||||
@NonNull InsightsBannerTier insightsBannerTier,
|
||||
@Nullable byte[] storageId,
|
||||
@Nullable byte[] identityKey,
|
||||
@NonNull IdentityDatabase.VerifiedStatus identityStatus)
|
||||
@NonNull IdentityDatabase.VerifiedStatus identityStatus,
|
||||
@NonNull MentionSetting mentionSetting)
|
||||
{
|
||||
this.id = id;
|
||||
this.uuid = uuid;
|
||||
@@ -2505,6 +2565,7 @@ public class RecipientDatabase extends Database {
|
||||
this.storageId = storageId;
|
||||
this.identityKey = identityKey;
|
||||
this.identityStatus = identityStatus;
|
||||
this.mentionSetting = mentionSetting;
|
||||
}
|
||||
|
||||
public RecipientId getId() {
|
||||
@@ -2661,6 +2722,10 @@ public class RecipientDatabase extends Database {
|
||||
public @NonNull IdentityDatabase.VerifiedStatus getIdentityStatus() {
|
||||
return identityStatus;
|
||||
}
|
||||
|
||||
public @NonNull MentionSetting getMentionSetting() {
|
||||
return mentionSetting;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RecipientReader implements Closeable {
|
||||
|
||||
@@ -66,7 +66,7 @@ public final class ThreadBodyUtil {
|
||||
return context.getString(R.string.ThreadRecord_media_message);
|
||||
} else {
|
||||
Log.w(TAG, "Got a media message with a body of a type we were not able to process. [contains media slide]:" + record.containsMediaSlide());
|
||||
return record.getBody();
|
||||
return getBody(context, record);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,10 +75,10 @@ public final class ThreadBodyUtil {
|
||||
}
|
||||
|
||||
private static @NonNull String getBodyOrDefault(@NonNull Context context, @NonNull MessageRecord record, @StringRes int defaultStringRes) {
|
||||
if (TextUtils.isEmpty(record.getBody())) {
|
||||
return context.getString(defaultStringRes);
|
||||
} else {
|
||||
return record.getBody();
|
||||
}
|
||||
return TextUtils.isEmpty(record.getBody()) ? context.getString(defaultStringRes) : getBody(context, record);
|
||||
}
|
||||
|
||||
private static @NonNull String getBody(@NonNull Context context, @NonNull MessageRecord record) {
|
||||
return MentionUtil.updateBodyWithDisplayNames(context, record, record.getBody()).toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import net.sqlcipher.database.SQLiteDatabaseHook;
|
||||
import net.sqlcipher.database.SQLiteOpenHelper;
|
||||
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactColorsLegacy;
|
||||
import org.thoughtcrime.securesms.database.MentionDatabase;
|
||||
import org.thoughtcrime.securesms.database.RemappedRecordsDatabase;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
@@ -139,8 +140,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
private static final int QUOTE_CLEANUP = 65;
|
||||
private static final int BORDERLESS = 66;
|
||||
private static final int REMAPPED_RECORDS = 67;
|
||||
private static final int MENTIONS = 68;
|
||||
|
||||
private static final int DATABASE_VERSION = 67;
|
||||
private static final int DATABASE_VERSION = 68;
|
||||
private static final String DATABASE_NAME = "signal.db";
|
||||
|
||||
private final Context context;
|
||||
@@ -184,6 +186,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(StorageKeyDatabase.CREATE_TABLE);
|
||||
db.execSQL(KeyValueDatabase.CREATE_TABLE);
|
||||
db.execSQL(MegaphoneDatabase.CREATE_TABLE);
|
||||
db.execSQL(MentionDatabase.CREATE_TABLE);
|
||||
executeStatements(db, SearchDatabase.CREATE_TABLE);
|
||||
executeStatements(db, JobDatabase.CREATE_TABLE);
|
||||
executeStatements(db, RemappedRecordsDatabase.CREATE_TABLE);
|
||||
@@ -198,6 +201,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES);
|
||||
executeStatements(db, StickerDatabase.CREATE_INDEXES);
|
||||
executeStatements(db, StorageKeyDatabase.CREATE_INDEXES);
|
||||
executeStatements(db, MentionDatabase.CREATE_INDEXES);
|
||||
|
||||
if (context.getDatabasePath(ClassicOpenHelper.NAME).exists()) {
|
||||
ClassicOpenHelper legacyHelper = new ClassicOpenHelper(context);
|
||||
@@ -970,6 +974,23 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
"new_id INTEGER)");
|
||||
}
|
||||
|
||||
if (oldVersion < MENTIONS) {
|
||||
db.execSQL("CREATE TABLE mention (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
"thread_id INTEGER, " +
|
||||
"message_id INTEGER, " +
|
||||
"recipient_id INTEGER, " +
|
||||
"range_start INTEGER, " +
|
||||
"range_length INTEGER)");
|
||||
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS mention_message_id_index ON mention (message_id)");
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS mention_recipient_id_thread_id_index ON mention (recipient_id, thread_id);");
|
||||
|
||||
db.execSQL("ALTER TABLE mms ADD COLUMN quote_mentions BLOB DEFAULT NULL");
|
||||
db.execSQL("ALTER TABLE mms ADD COLUMN mentions_self INTEGER DEFAULT 0");
|
||||
|
||||
db.execSQL("ALTER TABLE recipient ADD COLUMN mention_setting INTEGER DEFAULT 0");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
||||
@@ -45,6 +45,7 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
|
||||
private final static String TAG = MediaMmsMessageRecord.class.getSimpleName();
|
||||
|
||||
private final int partCount;
|
||||
private final boolean mentionsSelf;
|
||||
|
||||
public MediaMmsMessageRecord(long id,
|
||||
Recipient conversationRecipient,
|
||||
@@ -71,19 +72,26 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
|
||||
@NonNull List<LinkPreview> linkPreviews,
|
||||
boolean unidentified,
|
||||
@NonNull List<ReactionRecord> reactions,
|
||||
boolean remoteDelete)
|
||||
boolean remoteDelete,
|
||||
boolean mentionsSelf)
|
||||
{
|
||||
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent,
|
||||
dateReceived, dateServer, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
|
||||
subscriptionId, expiresIn, expireStarted, viewOnce, slideDeck,
|
||||
readReceiptCount, quote, contacts, linkPreviews, unidentified, reactions, remoteDelete);
|
||||
this.partCount = partCount;
|
||||
this.partCount = partCount;
|
||||
this.mentionsSelf = mentionsSelf;
|
||||
}
|
||||
|
||||
public int getPartCount() {
|
||||
return partCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSelfMention() {
|
||||
return mentionsSelf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMmsNotification() {
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.thoughtcrime.securesms.database.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Mention implements Comparable<Mention>, Parcelable {
|
||||
private final RecipientId recipientId;
|
||||
private final int start;
|
||||
private final int length;
|
||||
|
||||
public Mention(@NonNull RecipientId recipientId, int start, int length) {
|
||||
this.recipientId = recipientId;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
protected Mention(Parcel in) {
|
||||
recipientId = in.readParcelable(RecipientId.class.getClassLoader());
|
||||
start = in.readInt();
|
||||
length = in.readInt();
|
||||
}
|
||||
|
||||
public @NonNull RecipientId getRecipientId() {
|
||||
return recipientId;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Mention other) {
|
||||
return Integer.compare(start, other.start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(recipientId, start, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (object == null || getClass() != object.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Mention that = (Mention) object;
|
||||
return recipientId.equals(that.recipientId) && start == that.start && length == that.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeParcelable(recipientId, flags);
|
||||
dest.writeInt(start);
|
||||
dest.writeInt(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Creator<Mention> CREATOR = new Creator<Mention>() {
|
||||
@Override
|
||||
public Mention createFromParcel(Parcel in) {
|
||||
return new Mention(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mention[] newArray(int size) {
|
||||
return new Mention[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -374,4 +374,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
public @NonNull List<ReactionRecord> getReactions() {
|
||||
return reactions;
|
||||
}
|
||||
|
||||
public boolean hasSelfMention() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,42 @@
|
||||
package org.thoughtcrime.securesms.database.model;
|
||||
|
||||
import android.text.SpannableString;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Quote {
|
||||
|
||||
private final long id;
|
||||
private final RecipientId author;
|
||||
private final String text;
|
||||
private final boolean missing;
|
||||
private final SlideDeck attachment;
|
||||
private final long id;
|
||||
private final RecipientId author;
|
||||
private final CharSequence text;
|
||||
private final boolean missing;
|
||||
private final SlideDeck attachment;
|
||||
private final List<Mention> mentions;
|
||||
|
||||
public Quote(long id, @NonNull RecipientId author, @Nullable String text, boolean missing, @NonNull SlideDeck attachment) {
|
||||
this.id = id;
|
||||
this.author = author;
|
||||
this.text = text;
|
||||
this.missing = missing;
|
||||
this.attachment = attachment;
|
||||
public Quote(long id,
|
||||
@NonNull RecipientId author,
|
||||
@Nullable CharSequence text,
|
||||
boolean missing,
|
||||
@NonNull SlideDeck attachment,
|
||||
@NonNull List<Mention> mentions)
|
||||
{
|
||||
this.id = id;
|
||||
this.author = author;
|
||||
this.missing = missing;
|
||||
this.attachment = attachment;
|
||||
this.mentions = mentions;
|
||||
|
||||
SpannableString spannable = new SpannableString(text);
|
||||
MentionAnnotation.setMentionAnnotations(spannable, mentions);
|
||||
|
||||
this.text = spannable;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
@@ -31,7 +47,7 @@ public class Quote {
|
||||
return author;
|
||||
}
|
||||
|
||||
public @Nullable String getText() {
|
||||
public @Nullable CharSequence getDisplayText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user