Add mention detection to search flows.
This commit is contained in:
parent
5cd4b82ed0
commit
d563de4207
7 changed files with 323 additions and 56 deletions
|
@ -11,20 +11,29 @@ public class MessageResult {
|
|||
|
||||
public final Recipient conversationRecipient;
|
||||
public final Recipient messageRecipient;
|
||||
public final String body;
|
||||
public final String bodySnippet;
|
||||
public final long threadId;
|
||||
public final long messageId;
|
||||
public final long receivedTimestampMs;
|
||||
public final boolean isMms;
|
||||
|
||||
public MessageResult(@NonNull Recipient conversationRecipient,
|
||||
@NonNull Recipient messageRecipient,
|
||||
@NonNull String body,
|
||||
@NonNull String bodySnippet,
|
||||
long threadId,
|
||||
long receivedTimestampMs)
|
||||
long messageId,
|
||||
long receivedTimestampMs,
|
||||
boolean isMms)
|
||||
{
|
||||
this.conversationRecipient = conversationRecipient;
|
||||
this.messageRecipient = messageRecipient;
|
||||
this.body = body;
|
||||
this.bodySnippet = bodySnippet;
|
||||
this.threadId = threadId;
|
||||
this.messageId = messageId;
|
||||
this.receivedTimestampMs = receivedTimestampMs;
|
||||
this.isMms = isMms;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@ import android.database.Cursor;
|
|||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
|
@ -15,7 +18,6 @@ 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;
|
||||
|
@ -86,27 +88,58 @@ public class MentionDatabase extends Database {
|
|||
}
|
||||
|
||||
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);
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
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);
|
||||
return readMentions(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
if (messageMentions == null) {
|
||||
messageMentions = new LinkedList<>();
|
||||
mentions.put(messageId, messageMentions);
|
||||
}
|
||||
public @NonNull Map<Long, List<Mention>> getMentionsContainingRecipients(@NonNull Collection<RecipientId> recipientIds, long limit) {
|
||||
return getMentionsContainingRecipients(recipientIds, -1, limit);
|
||||
}
|
||||
|
||||
messageMentions.add(new Mention(RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID)),
|
||||
CursorUtil.requireInt(cursor, RANGE_START),
|
||||
CursorUtil.requireInt(cursor, RANGE_LENGTH)));
|
||||
}
|
||||
public @NonNull Map<Long, List<Mention>> getMentionsContainingRecipients(@NonNull Collection<RecipientId> recipientIds, long threadId, long limit) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
String ids = TextUtils.join(",", Stream.of(recipientIds).map(RecipientId::serialize).toList());
|
||||
|
||||
String where = " WHERE " + RECIPIENT_ID + " IN (" + ids + ")";
|
||||
if (threadId != -1) {
|
||||
where += " AND " + THREAD_ID + " = " + threadId;
|
||||
}
|
||||
|
||||
String subSelect = "SELECT DISTINCT " + MESSAGE_ID +
|
||||
" FROM " + TABLE_NAME +
|
||||
where +
|
||||
" ORDER BY " + ID + " DESC" +
|
||||
" LIMIT " + limit;
|
||||
|
||||
String query = "SELECT *" +
|
||||
" FROM " + TABLE_NAME +
|
||||
" WHERE " + MESSAGE_ID +
|
||||
" IN (" + subSelect + ")";
|
||||
|
||||
try (Cursor cursor = db.rawQuery(query, null)) {
|
||||
return readMentions(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull Map<Long, List<Mention>> readMentions(@Nullable Cursor cursor) {
|
||||
Map<Long, List<Mention>> mentions = new HashMap<>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -470,6 +470,11 @@ public class MmsDatabase extends MessagingDatabase {
|
|||
}
|
||||
}
|
||||
|
||||
public Reader getMessages(Collection<Long> messageIds) {
|
||||
String ids = TextUtils.join(",", messageIds);
|
||||
return readerFor(rawQuery(MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " IN (" + ids + ")", null));
|
||||
}
|
||||
|
||||
public Reader getExpireStartedMessages() {
|
||||
String where = EXPIRE_STARTED + " > 0";
|
||||
return readerFor(rawQuery(where, null));
|
||||
|
|
|
@ -1933,17 +1933,24 @@ 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()) {
|
||||
public @NonNull List<Recipient> queryRecipientsForMentions(@NonNull String query) {
|
||||
return queryRecipientsForMentions(query, null);
|
||||
}
|
||||
|
||||
public @NonNull List<Recipient> queryRecipientsForMentions(@NonNull String query, @Nullable List<RecipientId> recipientIds) {
|
||||
if (TextUtils.isEmpty(query)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
query = buildCaseInsensitiveGlobPattern(query);
|
||||
|
||||
String ids = TextUtils.join(",", Stream.of(recipientIds).map(RecipientId::serialize).toList());
|
||||
String ids = null;
|
||||
if (Util.hasItems(recipientIds)) {
|
||||
ids = TextUtils.join(",", Stream.of(recipientIds).map(RecipientId::serialize).toList());
|
||||
}
|
||||
|
||||
String selection = BLOCKED + " = 0 AND " +
|
||||
ID + " IN (" + ids + ") AND " +
|
||||
(ids != null ? ID + " IN (" + ids + ") AND " : "") +
|
||||
SORT_NAME + " GLOB ?";
|
||||
|
||||
List<Recipient> recipients = new ArrayList<>();
|
||||
|
|
|
@ -26,6 +26,10 @@ public class SearchDatabase extends Database {
|
|||
public static final String SNIPPET = "snippet";
|
||||
public static final String CONVERSATION_RECIPIENT = "conversation_recipient";
|
||||
public static final String MESSAGE_RECIPIENT = "message_recipient";
|
||||
public static final String IS_MMS = "is_mms";
|
||||
public static final String MESSAGE_ID = "message_id";
|
||||
|
||||
public static final String SNIPPET_WRAP = "...";
|
||||
|
||||
public static final String[] CREATE_TABLE = {
|
||||
"CREATE VIRTUAL TABLE " + SMS_FTS_TABLE_NAME + " USING fts5(" + BODY + ", " + THREAD_ID + " UNINDEXED, content=" + SmsDatabase.TABLE_NAME + ", content_rowid=" + SmsDatabase.ID + ");",
|
||||
|
@ -60,9 +64,12 @@ public class SearchDatabase extends Database {
|
|||
"SELECT " +
|
||||
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID + " AS " + CONVERSATION_RECIPIENT + ", " +
|
||||
MmsSmsColumns.RECIPIENT_ID + " AS " + MESSAGE_RECIPIENT + ", " +
|
||||
"snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
|
||||
"snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '" + SNIPPET_WRAP + "', 7) AS " + SNIPPET + ", " +
|
||||
SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " +
|
||||
SMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
|
||||
SMS_FTS_TABLE_NAME + "." + THREAD_ID + ", " +
|
||||
SMS_FTS_TABLE_NAME + "." + BODY + ", " +
|
||||
SMS_FTS_TABLE_NAME + "." + ID + " AS " + MESSAGE_ID + ", " +
|
||||
"0 AS " + IS_MMS + " " +
|
||||
"FROM " + SmsDatabase.TABLE_NAME + " " +
|
||||
"INNER JOIN " + SMS_FTS_TABLE_NAME + " ON " + SMS_FTS_TABLE_NAME + "." + ID + " = " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.ID + " " +
|
||||
"INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + SMS_FTS_TABLE_NAME + "." + THREAD_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " " +
|
||||
|
@ -71,9 +78,12 @@ public class SearchDatabase extends Database {
|
|||
"SELECT " +
|
||||
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID + " AS " + CONVERSATION_RECIPIENT + ", " +
|
||||
MmsSmsColumns.RECIPIENT_ID + " AS " + MESSAGE_RECIPIENT + ", " +
|
||||
"snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
|
||||
"snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '" + SNIPPET_WRAP + "', 7) AS " + SNIPPET + ", " +
|
||||
MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " +
|
||||
MMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
|
||||
MMS_FTS_TABLE_NAME + "." + THREAD_ID + ", " +
|
||||
MMS_FTS_TABLE_NAME + "." + BODY + ", " +
|
||||
MMS_FTS_TABLE_NAME + "." + ID + " AS " + MESSAGE_ID + ", " +
|
||||
"1 AS " + IS_MMS + " " +
|
||||
"FROM " + MmsDatabase.TABLE_NAME + " " +
|
||||
"INNER JOIN " + MMS_FTS_TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " " +
|
||||
"INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " " +
|
||||
|
@ -85,9 +95,12 @@ public class SearchDatabase extends Database {
|
|||
"SELECT " +
|
||||
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID + " AS " + CONVERSATION_RECIPIENT + ", " +
|
||||
MmsSmsColumns.RECIPIENT_ID + " AS " + MESSAGE_RECIPIENT + ", " +
|
||||
"snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
|
||||
"snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '" + SNIPPET_WRAP + "', 7) AS " + SNIPPET + ", " +
|
||||
SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " +
|
||||
SMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
|
||||
SMS_FTS_TABLE_NAME + "." + THREAD_ID + ", " +
|
||||
SMS_FTS_TABLE_NAME + "." + BODY + ", " +
|
||||
SMS_FTS_TABLE_NAME + "." + ID + " AS " + MESSAGE_ID + ", " +
|
||||
"0 AS " + IS_MMS + " " +
|
||||
"FROM " + SmsDatabase.TABLE_NAME + " " +
|
||||
"INNER JOIN " + SMS_FTS_TABLE_NAME + " ON " + SMS_FTS_TABLE_NAME + "." + ID + " = " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.ID + " " +
|
||||
"INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + SMS_FTS_TABLE_NAME + "." + THREAD_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " " +
|
||||
|
@ -96,9 +109,12 @@ public class SearchDatabase extends Database {
|
|||
"SELECT " +
|
||||
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID + " AS " + CONVERSATION_RECIPIENT + ", " +
|
||||
MmsSmsColumns.RECIPIENT_ID + " AS " + MESSAGE_RECIPIENT + ", " +
|
||||
"snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
|
||||
"snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '" + SNIPPET_WRAP + "', 7) AS " + SNIPPET + ", " +
|
||||
MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " +
|
||||
MMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
|
||||
MMS_FTS_TABLE_NAME + "." + THREAD_ID + ", " +
|
||||
MMS_FTS_TABLE_NAME + "." + BODY + ", " +
|
||||
MMS_FTS_TABLE_NAME + "." + ID + " AS " + MESSAGE_ID + ", " +
|
||||
"1 AS " + IS_MMS + " " +
|
||||
"FROM " + MmsDatabase.TABLE_NAME + " " +
|
||||
"INNER JOIN " + MMS_FTS_TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " " +
|
||||
"INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " " +
|
||||
|
|
|
@ -4,41 +4,51 @@ import android.content.Context;
|
|||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.MergeCursor;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.contacts.ContactRepository;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.MessageResult;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.SearchResult;
|
||||
import org.thoughtcrime.securesms.database.CursorList;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MentionDatabase;
|
||||
import org.thoughtcrime.securesms.database.MentionUtil;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.SearchDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.MessageResult;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.SearchResult;
|
||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static org.thoughtcrime.securesms.database.SearchDatabase.SNIPPET_WRAP;
|
||||
|
||||
/**
|
||||
* Manages data retrieval for search.
|
||||
*/
|
||||
|
@ -70,11 +80,17 @@ public class SearchRepository {
|
|||
private final ContactAccessor contactAccessor;
|
||||
private final Executor serialExecutor;
|
||||
private final ExecutorService parallelExecutor;
|
||||
private final RecipientDatabase recipientDatabase;
|
||||
private final MentionDatabase mentionDatabase;
|
||||
private final MmsDatabase mmsDatabase;
|
||||
|
||||
public SearchRepository() {
|
||||
this.context = ApplicationDependencies.getApplication().getApplicationContext();
|
||||
this.searchDatabase = DatabaseFactory.getSearchDatabase(context);
|
||||
this.threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
this.recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
this.mentionDatabase = DatabaseFactory.getMentionDatabase(context);
|
||||
this.mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||
this.contactRepository = new ContactRepository(context);
|
||||
this.contactAccessor = ContactAccessor.getInstance();
|
||||
this.serialExecutor = SignalExecutors.SERIAL;
|
||||
|
@ -90,13 +106,14 @@ public class SearchRepository {
|
|||
serialExecutor.execute(() -> {
|
||||
String cleanQuery = sanitizeQuery(query);
|
||||
|
||||
Future<List<Recipient>> contacts = parallelExecutor.submit(() -> queryContacts(cleanQuery));
|
||||
Future<List<ThreadRecord>> conversations = parallelExecutor.submit(() -> queryConversations(cleanQuery));
|
||||
Future<List<MessageResult>> messages = parallelExecutor.submit(() -> queryMessages(cleanQuery));
|
||||
Future<List<Recipient>> contacts = parallelExecutor.submit(() -> queryContacts(cleanQuery));
|
||||
Future<List<ThreadRecord>> conversations = parallelExecutor.submit(() -> queryConversations(cleanQuery));
|
||||
Future<List<MessageResult>> messages = parallelExecutor.submit(() -> queryMessages(cleanQuery));
|
||||
Future<List<MessageResult>> mentionMessages = parallelExecutor.submit(() -> queryMentions(sanitizeQueryAsTokens(query)));
|
||||
|
||||
try {
|
||||
long startTime = System.currentTimeMillis();
|
||||
SearchResult result = new SearchResult(cleanQuery, contacts.get(), conversations.get(), messages.get());
|
||||
SearchResult result = new SearchResult(cleanQuery, contacts.get(), conversations.get(), mergeMessagesAndMentions(messages.get(), mentionMessages.get()));
|
||||
|
||||
Log.d(TAG, "Total time: " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
|
||||
|
@ -115,11 +132,13 @@ public class SearchRepository {
|
|||
}
|
||||
|
||||
serialExecutor.execute(() -> {
|
||||
long startTime = System.currentTimeMillis();
|
||||
List<MessageResult> messages = queryMessages(sanitizeQuery(query), threadId);
|
||||
long startTime = System.currentTimeMillis();
|
||||
List<MessageResult> messages = queryMessages(sanitizeQuery(query), threadId);
|
||||
List<MessageResult> mentionMessages = queryMentions(sanitizeQueryAsTokens(query), threadId);
|
||||
|
||||
Log.d(TAG, "[ConversationQuery] " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
|
||||
callback.onResult(messages);
|
||||
callback.onResult(mergeMessagesAndMentions(messages, mentionMessages));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -150,17 +169,163 @@ public class SearchRepository {
|
|||
}
|
||||
|
||||
private @NonNull List<MessageResult> queryMessages(@NonNull String query) {
|
||||
List<MessageResult> results;
|
||||
try (Cursor cursor = searchDatabase.queryMessages(query)) {
|
||||
return readToList(cursor, new MessageModelBuilder(context));
|
||||
results = readToList(cursor, new MessageModelBuilder());
|
||||
}
|
||||
|
||||
List<Long> messageIds = new LinkedList<>();
|
||||
for (MessageResult result : results) {
|
||||
if (result.isMms) {
|
||||
messageIds.add(result.messageId);
|
||||
}
|
||||
}
|
||||
|
||||
if (messageIds.isEmpty()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
Map<Long, List<Mention>> mentions = DatabaseFactory.getMentionDatabase(context).getMentionsForMessages(messageIds);
|
||||
if (mentions.isEmpty()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
List<MessageResult> updatedResults = new ArrayList<>(results.size());
|
||||
for (MessageResult result : results) {
|
||||
if (result.isMms && mentions.containsKey(result.messageId)) {
|
||||
List<Mention> messageMentions = mentions.get(result.messageId);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
String updatedBody = MentionUtil.updateBodyAndMentionsWithDisplayNames(context, result.body, messageMentions).getBody().toString();
|
||||
String updatedSnippet = updateSnippetWithDisplayNames(result.body, result.bodySnippet, messageMentions);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
updatedResults.add(new MessageResult(result.conversationRecipient, result.messageRecipient, updatedBody, updatedSnippet, result.threadId, result.messageId, result.receivedTimestampMs, result.isMms));
|
||||
} else {
|
||||
updatedResults.add(result);
|
||||
}
|
||||
}
|
||||
|
||||
return updatedResults;
|
||||
}
|
||||
|
||||
private @NonNull String updateSnippetWithDisplayNames(@NonNull String body, @NonNull String bodySnippet, @NonNull List<Mention> mentions) {
|
||||
String cleanSnippet = bodySnippet;
|
||||
int startOffset = 0;
|
||||
|
||||
if (cleanSnippet.startsWith(SNIPPET_WRAP)) {
|
||||
cleanSnippet = cleanSnippet.substring(SNIPPET_WRAP.length());
|
||||
startOffset = SNIPPET_WRAP.length();
|
||||
}
|
||||
|
||||
if (cleanSnippet.endsWith(SNIPPET_WRAP)) {
|
||||
cleanSnippet = cleanSnippet.substring(0, cleanSnippet.length() - SNIPPET_WRAP.length());
|
||||
}
|
||||
|
||||
int startIndex = body.indexOf(cleanSnippet);
|
||||
|
||||
if (startIndex != -1) {
|
||||
List<Mention> adjustMentions = new ArrayList<>(mentions.size());
|
||||
for (Mention mention : mentions) {
|
||||
int adjustedStart = mention.getStart() - startIndex + startOffset;
|
||||
if (adjustedStart >= 0 && adjustedStart + mention.getLength() <= cleanSnippet.length()) {
|
||||
adjustMentions.add(new Mention(mention.getRecipientId(), adjustedStart, mention.getLength()));
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
return MentionUtil.updateBodyAndMentionsWithDisplayNames(context, bodySnippet, adjustMentions).getBody().toString();
|
||||
}
|
||||
|
||||
return bodySnippet;
|
||||
}
|
||||
|
||||
private @NonNull List<MessageResult> queryMessages(@NonNull String query, long threadId) {
|
||||
try (Cursor cursor = searchDatabase.queryMessages(query, threadId)) {
|
||||
return readToList(cursor, new MessageModelBuilder(context));
|
||||
return readToList(cursor, new MessageModelBuilder());
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull List<MessageResult> queryMentions(@NonNull List<String> cleanQueries) {
|
||||
Set<RecipientId> recipientIds = new HashSet<>();
|
||||
for (String cleanQuery : cleanQueries) {
|
||||
for (Recipient recipient : recipientDatabase.queryRecipientsForMentions(cleanQuery)) {
|
||||
recipientIds.add(recipient.getId());
|
||||
}
|
||||
}
|
||||
|
||||
Map<Long, List<Mention>> mentionQueryResults = mentionDatabase.getMentionsContainingRecipients(recipientIds, 500);
|
||||
|
||||
if (mentionQueryResults.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<MessageResult> results = new ArrayList<>();
|
||||
|
||||
try (MmsDatabase.Reader reader = mmsDatabase.getMessages(mentionQueryResults.keySet())) {
|
||||
MessageRecord record;
|
||||
while ((record = reader.getNext()) != null) {
|
||||
List<Mention> mentions = mentionQueryResults.get(record.getId());
|
||||
if (Util.hasItems(mentions)) {
|
||||
MentionUtil.UpdatedBodyAndMentions updated = MentionUtil.updateBodyAndMentionsWithDisplayNames(context, record.getBody(), mentions);
|
||||
String updatedBody = updated.getBody() != null ? updated.getBody().toString() : record.getBody();
|
||||
String updatedSnippet = makeSnippet(cleanQueries, updatedBody);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
results.add(new MessageResult(threadDatabase.getRecipientForThreadId(record.getThreadId()), record.getRecipient(), updatedBody, updatedSnippet, record.getThreadId(), record.getId(), record.getDateReceived(), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private @NonNull List<MessageResult> queryMentions(@NonNull List<String> cleanQueries, long threadId) {
|
||||
Set<RecipientId> recipientIds = new HashSet<>();
|
||||
for (String cleanQuery : cleanQueries) {
|
||||
for (Recipient recipient : recipientDatabase.queryRecipientsForMentions(cleanQuery)) {
|
||||
recipientIds.add(recipient.getId());
|
||||
}
|
||||
}
|
||||
|
||||
Map<Long, List<Mention>> mentionQueryResults = mentionDatabase.getMentionsContainingRecipients(recipientIds, threadId, 500);
|
||||
|
||||
if (mentionQueryResults.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<MessageResult> results = new ArrayList<>();
|
||||
|
||||
try (MmsDatabase.Reader reader = mmsDatabase.getMessages(mentionQueryResults.keySet())) {
|
||||
MessageRecord record;
|
||||
while ((record = reader.getNext()) != null) {
|
||||
//noinspection ConstantConditions
|
||||
results.add(new MessageResult(threadDatabase.getRecipientForThreadId(record.getThreadId()), record.getRecipient(), record.getBody(), record.getBody(), record.getThreadId(), record.getId(), record.getDateReceived(), true));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private @NonNull String makeSnippet(@NonNull List<String> queries, @NonNull String body) {
|
||||
if (body.length() < 50) {
|
||||
return body;
|
||||
}
|
||||
|
||||
String lowerBody = body.toLowerCase();
|
||||
for (String query : queries) {
|
||||
int foundIndex = lowerBody.indexOf(query.toLowerCase());
|
||||
if (foundIndex != -1) {
|
||||
int snippetStart = Math.max(0, Math.max(body.lastIndexOf(' ', foundIndex - 5) + 1, foundIndex - 15));
|
||||
int lastSpace = body.indexOf(' ', foundIndex + 30);
|
||||
int snippetEnd = Math.min(body.length(), lastSpace > 0 ? Math.min(lastSpace, foundIndex + 40) : foundIndex + 40);
|
||||
|
||||
return (snippetStart > 0 ? SNIPPET_WRAP : "") + body.substring(snippetStart, snippetEnd) + (snippetEnd < body.length() ? SNIPPET_WRAP : "");
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
private @NonNull <T> List<T> readToList(@Nullable Cursor cursor, @NonNull CursorList.ModelBuilder<T> builder) {
|
||||
return readToList(cursor, builder, -1);
|
||||
}
|
||||
|
@ -203,6 +368,37 @@ public class SearchRepository {
|
|||
return out.toString();
|
||||
}
|
||||
|
||||
private @NonNull List<String> sanitizeQueryAsTokens(@NonNull String query) {
|
||||
String[] parts = query.split("\\s+");
|
||||
if (parts.length > 3) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return Stream.of(parts).map(this::sanitizeQuery).toList();
|
||||
}
|
||||
|
||||
private static @NonNull List<MessageResult> mergeMessagesAndMentions(@NonNull List<MessageResult> messages, @NonNull List<MessageResult> mentionMessages) {
|
||||
Set<Long> includedMmsMessages = new HashSet<>();
|
||||
|
||||
List<MessageResult> combined = new ArrayList<>(messages.size() + mentionMessages.size());
|
||||
for (MessageResult result : messages) {
|
||||
combined.add(result);
|
||||
if (result.isMms) {
|
||||
includedMmsMessages.add(result.messageId);
|
||||
}
|
||||
}
|
||||
|
||||
for (MessageResult result : mentionMessages) {
|
||||
if (!includedMmsMessages.contains(result.messageId)) {
|
||||
combined.add(result);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(combined, Collections.reverseOrder((left, right) -> Long.compare(left.receivedTimestampMs, right.receivedTimestampMs)));
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
private static class RecipientModelBuilder implements CursorList.ModelBuilder<Recipient> {
|
||||
|
||||
@Override
|
||||
|
@ -228,23 +424,20 @@ public class SearchRepository {
|
|||
|
||||
private static class MessageModelBuilder implements CursorList.ModelBuilder<MessageResult> {
|
||||
|
||||
private final Context context;
|
||||
|
||||
MessageModelBuilder(@NonNull Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageResult build(@NonNull Cursor cursor) {
|
||||
RecipientId conversationRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndex(SearchDatabase.CONVERSATION_RECIPIENT)));
|
||||
RecipientId messageRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(SearchDatabase.MESSAGE_RECIPIENT)));
|
||||
RecipientId messageRecipientId = RecipientId.from(CursorUtil.requireLong(cursor, SearchDatabase.MESSAGE_RECIPIENT));
|
||||
Recipient conversationRecipient = Recipient.live(conversationRecipientId).get();
|
||||
Recipient messageRecipient = Recipient.live(messageRecipientId).get();
|
||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.SNIPPET));
|
||||
long receivedMs = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_RECEIVED));
|
||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.THREAD_ID));
|
||||
String body = CursorUtil.requireString(cursor, SearchDatabase.BODY);
|
||||
String bodySnippet = CursorUtil.requireString(cursor, SearchDatabase.SNIPPET);
|
||||
long receivedMs = CursorUtil.requireLong(cursor, MmsSmsColumns.NORMALIZED_DATE_RECEIVED);
|
||||
long threadId = CursorUtil.requireLong(cursor, MmsSmsColumns.THREAD_ID);
|
||||
int messageId = CursorUtil.requireInt(cursor, SearchDatabase.MESSAGE_ID);
|
||||
boolean isMms = CursorUtil.requireInt(cursor, SearchDatabase.IS_MMS) == 1;
|
||||
|
||||
return new MessageResult(conversationRecipient, messageRecipient, body, threadId, receivedMs);
|
||||
return new MessageResult(conversationRecipient, messageRecipient, body, bodySnippet, threadId, messageId, receivedMs, isMms);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -155,10 +155,14 @@ public class Util {
|
|||
return value == null || value.getText() == null || TextUtils.isEmpty(value.getTextTrimmed());
|
||||
}
|
||||
|
||||
public static boolean isEmpty(Collection collection) {
|
||||
public static boolean isEmpty(Collection<?> collection) {
|
||||
return collection == null || collection.isEmpty();
|
||||
}
|
||||
|
||||
public static boolean hasItems(@Nullable Collection<?> collection) {
|
||||
return collection != null && !collection.isEmpty();
|
||||
}
|
||||
|
||||
public static <K, V> V getOrDefault(@NonNull Map<K, V> map, K key, V defaultValue) {
|
||||
return map.containsKey(key) ? map.get(key) : defaultValue;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue