Konvert searchRepository (#1071)
parent
6aa0024aa9
commit
f2cf7565e5
@ -1,285 +0,0 @@
|
||||
package org.thoughtcrime.securesms.search;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.MergeCursor;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.annimon.stream.Stream;
|
||||
import org.session.libsession.messaging.contacts.Contact;
|
||||
import org.session.libsession.utilities.Address;
|
||||
import org.session.libsession.utilities.GroupRecord;
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsession.utilities.recipients.Recipient;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.database.CursorList;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||
import org.thoughtcrime.securesms.database.SearchDatabase;
|
||||
import org.thoughtcrime.securesms.database.SessionContactDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.search.model.MessageResult;
|
||||
import org.thoughtcrime.securesms.search.model.SearchResult;
|
||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import kotlin.Pair;
|
||||
|
||||
// Class to manage data retrieval for search
|
||||
public class SearchRepository {
|
||||
private static final String TAG = SearchRepository.class.getSimpleName();
|
||||
|
||||
private static final Set<Character> BANNED_CHARACTERS = new HashSet<>();
|
||||
static {
|
||||
// Construct a list containing several ranges of invalid ASCII characters
|
||||
// See: https://www.ascii-code.com/
|
||||
for (int i = 33; i <= 47; i++) { BANNED_CHARACTERS.add((char) i); } // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /
|
||||
for (int i = 58; i <= 64; i++) { BANNED_CHARACTERS.add((char) i); } // :, ;, <, =, >, ?, @
|
||||
for (int i = 91; i <= 96; i++) { BANNED_CHARACTERS.add((char) i); } // [, \, ], ^, _, `
|
||||
for (int i = 123; i <= 126; i++) { BANNED_CHARACTERS.add((char) i); } // {, |, }, ~
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private final SearchDatabase searchDatabase;
|
||||
private final ThreadDatabase threadDatabase;
|
||||
private final GroupDatabase groupDatabase;
|
||||
private final SessionContactDatabase contactDatabase;
|
||||
private final ContactAccessor contactAccessor;
|
||||
private final Executor executor;
|
||||
|
||||
public SearchRepository(@NonNull Context context,
|
||||
@NonNull SearchDatabase searchDatabase,
|
||||
@NonNull ThreadDatabase threadDatabase,
|
||||
@NonNull GroupDatabase groupDatabase,
|
||||
@NonNull SessionContactDatabase contactDatabase,
|
||||
@NonNull ContactAccessor contactAccessor,
|
||||
@NonNull Executor executor)
|
||||
{
|
||||
this.context = context.getApplicationContext();
|
||||
this.searchDatabase = searchDatabase;
|
||||
this.threadDatabase = threadDatabase;
|
||||
this.groupDatabase = groupDatabase;
|
||||
this.contactDatabase = contactDatabase;
|
||||
this.contactAccessor = contactAccessor;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public void query(@NonNull String query, @NonNull Callback<SearchResult> callback) {
|
||||
// If the sanitized search is empty then abort without search
|
||||
String cleanQuery = sanitizeQuery(query).trim();
|
||||
|
||||
executor.execute(() -> {
|
||||
Stopwatch timer = new Stopwatch("FtsQuery");
|
||||
timer.split("clean");
|
||||
|
||||
Pair<CursorList<Contact>, List<String>> contacts = queryContacts(cleanQuery);
|
||||
timer.split("Contacts");
|
||||
|
||||
CursorList<GroupRecord> conversations = queryConversations(cleanQuery, contacts.getSecond());
|
||||
timer.split("Conversations");
|
||||
|
||||
CursorList<MessageResult> messages = queryMessages(cleanQuery);
|
||||
timer.split("Messages");
|
||||
|
||||
timer.stop(TAG);
|
||||
|
||||
callback.onResult(new SearchResult(cleanQuery, contacts.getFirst(), conversations, messages));
|
||||
});
|
||||
}
|
||||
|
||||
public void query(@NonNull String query, long threadId, @NonNull Callback<CursorList<MessageResult>> callback) {
|
||||
// If the sanitized search query is empty then abort the search
|
||||
String cleanQuery = sanitizeQuery(query).trim();
|
||||
if (cleanQuery.isEmpty()) {
|
||||
callback.onResult(CursorList.emptyList());
|
||||
return;
|
||||
}
|
||||
|
||||
executor.execute(() -> {
|
||||
CursorList<MessageResult> messages = queryMessages(cleanQuery, threadId);
|
||||
callback.onResult(messages);
|
||||
});
|
||||
}
|
||||
|
||||
public Pair<CursorList<Contact>, List<String>> queryContacts(String query) {
|
||||
Cursor contacts = contactDatabase.queryContactsByName(query);
|
||||
List<Address> contactList = new ArrayList<>();
|
||||
List<String> contactStrings = new ArrayList<>();
|
||||
|
||||
while (contacts.moveToNext()) {
|
||||
try {
|
||||
Contact contact = contactDatabase.contactFromCursor(contacts);
|
||||
String contactAccountId = contact.getAccountID();
|
||||
Address address = Address.fromSerialized(contactAccountId);
|
||||
contactList.add(address);
|
||||
contactStrings.add(contactAccountId);
|
||||
} catch (Exception e) {
|
||||
Log.e("Loki", "Error building Contact from cursor in query", e);
|
||||
}
|
||||
}
|
||||
|
||||
contacts.close();
|
||||
|
||||
Cursor addressThreads = threadDatabase.searchConversationAddresses(query);
|
||||
Cursor individualRecipients = threadDatabase.getFilteredConversationList(contactList);
|
||||
if (individualRecipients == null && addressThreads == null) {
|
||||
return new Pair<>(CursorList.emptyList(),contactStrings);
|
||||
}
|
||||
MergeCursor merged = new MergeCursor(new Cursor[]{addressThreads, individualRecipients});
|
||||
|
||||
return new Pair<>(new CursorList<>(merged, new ContactModelBuilder(contactDatabase, threadDatabase)), contactStrings);
|
||||
}
|
||||
|
||||
private CursorList<GroupRecord> queryConversations(@NonNull String query, List<String> matchingAddresses) {
|
||||
List<String> numbers = contactAccessor.getNumbersForThreadSearchFilter(context, query);
|
||||
String localUserNumber = TextSecurePreferences.getLocalNumber(context);
|
||||
if (localUserNumber != null) {
|
||||
matchingAddresses.remove(localUserNumber);
|
||||
}
|
||||
Set<Address> addresses = new HashSet<>(Stream.of(numbers).map(number -> Address.fromExternal(context, number)).toList());
|
||||
|
||||
Cursor membersGroupList = groupDatabase.getGroupsFilteredByMembers(matchingAddresses);
|
||||
if (membersGroupList != null) {
|
||||
GroupDatabase.Reader reader = new GroupDatabase.Reader(membersGroupList);
|
||||
while (membersGroupList.moveToNext()) {
|
||||
GroupRecord record = reader.getCurrent();
|
||||
if (record == null) continue;
|
||||
|
||||
addresses.add(Address.fromSerialized(record.getEncodedId()));
|
||||
}
|
||||
membersGroupList.close();
|
||||
}
|
||||
|
||||
Cursor conversations = threadDatabase.getFilteredConversationList(new ArrayList<>(addresses));
|
||||
return conversations != null ? new CursorList<>(conversations, new GroupModelBuilder(threadDatabase, groupDatabase))
|
||||
: CursorList.emptyList();
|
||||
}
|
||||
|
||||
private CursorList<MessageResult> queryMessages(@NonNull String query) {
|
||||
Cursor messages = searchDatabase.queryMessages(query);
|
||||
return messages != null ? new CursorList<>(messages, new MessageModelBuilder(context))
|
||||
: CursorList.emptyList();
|
||||
}
|
||||
|
||||
private CursorList<MessageResult> queryMessages(@NonNull String query, long threadId) {
|
||||
Cursor messages = searchDatabase.queryMessages(query, threadId);
|
||||
return messages != null ? new CursorList<>(messages, new MessageModelBuilder(context))
|
||||
: CursorList.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfortunately {@link DatabaseUtils#sqlEscapeString(String)} is not sufficient for our purposes.
|
||||
* MATCH queries have a separate format of their own that disallow most "special" characters.
|
||||
*
|
||||
* Also, SQLite can't search for apostrophes, meaning we can't normally find words like "I'm".
|
||||
* However, if we replace the apostrophe with a space, then the query will find the match.
|
||||
*/
|
||||
private String sanitizeQuery(@NonNull String query) {
|
||||
StringBuilder out = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < query.length(); i++) {
|
||||
char c = query.charAt(i);
|
||||
if (!BANNED_CHARACTERS.contains(c)) {
|
||||
out.append(c);
|
||||
} else if (c == '\'') {
|
||||
out.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static class ContactModelBuilder implements CursorList.ModelBuilder<Contact> {
|
||||
|
||||
private final SessionContactDatabase contactDb;
|
||||
private final ThreadDatabase threadDb;
|
||||
|
||||
public ContactModelBuilder(SessionContactDatabase contactDb, ThreadDatabase threadDb) {
|
||||
this.contactDb = contactDb;
|
||||
this.threadDb = threadDb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact build(@NonNull Cursor cursor) {
|
||||
ThreadRecord threadRecord = threadDb.readerFor(cursor).getCurrent();
|
||||
Contact contact = contactDb.getContactWithAccountID(threadRecord.getRecipient().getAddress().toString());
|
||||
if (contact == null) {
|
||||
contact = new Contact(threadRecord.getRecipient().getAddress().toString());
|
||||
contact.setThreadID(threadRecord.getThreadId());
|
||||
}
|
||||
return contact;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RecipientModelBuilder implements CursorList.ModelBuilder<Recipient> {
|
||||
|
||||
private final Context context;
|
||||
|
||||
RecipientModelBuilder(@NonNull Context context) { this.context = context; }
|
||||
|
||||
@Override
|
||||
public Recipient build(@NonNull Cursor cursor) {
|
||||
Address address = Address.fromExternal(context, cursor.getString(1));
|
||||
return Recipient.from(context, address, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static class GroupModelBuilder implements CursorList.ModelBuilder<GroupRecord> {
|
||||
private final ThreadDatabase threadDatabase;
|
||||
private final GroupDatabase groupDatabase;
|
||||
|
||||
public GroupModelBuilder(ThreadDatabase threadDatabase, GroupDatabase groupDatabase) {
|
||||
this.threadDatabase = threadDatabase;
|
||||
this.groupDatabase = groupDatabase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupRecord build(@NonNull Cursor cursor) {
|
||||
ThreadRecord threadRecord = threadDatabase.readerFor(cursor).getCurrent();
|
||||
return groupDatabase.getGroup(threadRecord.getRecipient().getAddress().toGroupString()).get();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ThreadModelBuilder implements CursorList.ModelBuilder<ThreadRecord> {
|
||||
|
||||
private final ThreadDatabase threadDatabase;
|
||||
|
||||
ThreadModelBuilder(@NonNull ThreadDatabase threadDatabase) {
|
||||
this.threadDatabase = threadDatabase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadRecord build(@NonNull Cursor cursor) {
|
||||
return threadDatabase.readerFor(cursor).getCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
Address conversationAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.CONVERSATION_ADDRESS)));
|
||||
Address messageAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.MESSAGE_ADDRESS)));
|
||||
Recipient conversationRecipient = Recipient.from(context, conversationAddress, false);
|
||||
Recipient messageRecipient = Recipient.from(context, messageAddress, false);
|
||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.SNIPPET));
|
||||
long sentMs = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_SENT));
|
||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.THREAD_ID));
|
||||
|
||||
return new MessageResult(conversationRecipient, messageRecipient, body, threadId, sentMs);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Callback<E> {
|
||||
void onResult(@NonNull E result);
|
||||
}
|
||||
}
|
@ -0,0 +1,262 @@
|
||||
package org.thoughtcrime.securesms.search
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.database.MergeCursor
|
||||
import com.annimon.stream.Stream
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.Address.Companion.fromExternal
|
||||
import org.session.libsession.utilities.Address.Companion.fromSerialized
|
||||
import org.session.libsession.utilities.GroupRecord
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor
|
||||
import org.thoughtcrime.securesms.database.CursorList
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase
|
||||
import org.thoughtcrime.securesms.database.MmsSmsColumns
|
||||
import org.thoughtcrime.securesms.database.SearchDatabase
|
||||
import org.thoughtcrime.securesms.database.SessionContactDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||
import org.thoughtcrime.securesms.search.model.MessageResult
|
||||
import org.thoughtcrime.securesms.search.model.SearchResult
|
||||
import org.thoughtcrime.securesms.util.Stopwatch
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
// Class to manage data retrieval for search
|
||||
class SearchRepository(
|
||||
context: Context,
|
||||
private val searchDatabase: SearchDatabase,
|
||||
private val threadDatabase: ThreadDatabase,
|
||||
private val groupDatabase: GroupDatabase,
|
||||
private val contactDatabase: SessionContactDatabase,
|
||||
private val contactAccessor: ContactAccessor,
|
||||
private val executor: Executor
|
||||
) {
|
||||
private val context: Context = context.applicationContext
|
||||
|
||||
fun query(query: String, callback: (SearchResult) -> Unit) {
|
||||
// If the sanitized search is empty then abort without search
|
||||
val cleanQuery = sanitizeQuery(query).trim { it <= ' ' }
|
||||
|
||||
executor.execute {
|
||||
val timer =
|
||||
Stopwatch("FtsQuery")
|
||||
timer.split("clean")
|
||||
|
||||
val contacts =
|
||||
queryContacts(cleanQuery)
|
||||
timer.split("Contacts")
|
||||
|
||||
val conversations =
|
||||
queryConversations(cleanQuery, contacts.second)
|
||||
timer.split("Conversations")
|
||||
|
||||
val messages = queryMessages(cleanQuery)
|
||||
timer.split("Messages")
|
||||
|
||||
timer.stop(TAG)
|
||||
callback(
|
||||
SearchResult(
|
||||
cleanQuery,
|
||||
contacts.first,
|
||||
conversations,
|
||||
messages
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun query(query: String, threadId: Long, callback: (CursorList<MessageResult?>) -> Unit) {
|
||||
// If the sanitized search query is empty then abort the search
|
||||
val cleanQuery = sanitizeQuery(query).trim { it <= ' ' }
|
||||
if (cleanQuery.isEmpty()) {
|
||||
callback(CursorList.emptyList())
|
||||
return
|
||||
}
|
||||
|
||||
executor.execute {
|
||||
val messages = queryMessages(cleanQuery, threadId)
|
||||
callback(messages)
|
||||
}
|
||||
}
|
||||
|
||||
fun queryContacts(query: String): Pair<CursorList<Contact>, MutableList<String>> {
|
||||
val contacts = contactDatabase.queryContactsByName(query)
|
||||
val contactList: MutableList<Address> = ArrayList()
|
||||
val contactStrings: MutableList<String> = ArrayList()
|
||||
|
||||
while (contacts.moveToNext()) {
|
||||
try {
|
||||
val contact = contactDatabase.contactFromCursor(contacts)
|
||||
val contactAccountId = contact.accountID
|
||||
val address = fromSerialized(contactAccountId)
|
||||
contactList.add(address)
|
||||
contactStrings.add(contactAccountId)
|
||||
} catch (e: Exception) {
|
||||
Log.e("Loki", "Error building Contact from cursor in query", e)
|
||||
}
|
||||
}
|
||||
|
||||
contacts.close()
|
||||
|
||||
val addressThreads = threadDatabase.searchConversationAddresses(query)
|
||||
val individualRecipients = threadDatabase.getFilteredConversationList(contactList)
|
||||
if (individualRecipients == null && addressThreads == null) {
|
||||
return Pair(CursorList.emptyList(), contactStrings)
|
||||
}
|
||||
val merged = MergeCursor(arrayOf(addressThreads, individualRecipients))
|
||||
|
||||
return Pair(
|
||||
CursorList(merged, ContactModelBuilder(contactDatabase, threadDatabase)),
|
||||
contactStrings
|
||||
)
|
||||
}
|
||||
|
||||
private fun queryConversations(
|
||||
query: String,
|
||||
matchingAddresses: MutableList<String>
|
||||
): CursorList<GroupRecord> {
|
||||
val numbers = contactAccessor.getNumbersForThreadSearchFilter(context, query)
|
||||
val localUserNumber = getLocalNumber(context)
|
||||
if (localUserNumber != null) {
|
||||
matchingAddresses.remove(localUserNumber)
|
||||
}
|
||||
val addresses: MutableSet<Address> = HashSet(Stream.of(numbers).map { number: String? ->
|
||||
fromExternal(
|
||||
context,
|
||||
number
|
||||
)
|
||||
}.toList())
|
||||
|
||||
val membersGroupList = groupDatabase.getGroupsFilteredByMembers(matchingAddresses)
|
||||
if (membersGroupList != null) {
|
||||
val reader = GroupDatabase.Reader(membersGroupList)
|
||||
while (membersGroupList.moveToNext()) {
|
||||
val record = reader.current ?: continue
|
||||
|
||||
addresses.add(fromSerialized(record.encodedId))
|
||||
}
|
||||
membersGroupList.close()
|
||||
}
|
||||
|
||||
val conversations = threadDatabase.getFilteredConversationList(ArrayList(addresses))
|
||||
return if (conversations != null)
|
||||
CursorList(conversations, GroupModelBuilder(threadDatabase, groupDatabase))
|
||||
else
|
||||
CursorList.emptyList()
|
||||
}
|
||||
|
||||
private fun queryMessages(query: String): CursorList<MessageResult> {
|
||||
val messages = searchDatabase.queryMessages(query)
|
||||
return if (messages != null)
|
||||
CursorList(messages, MessageModelBuilder(context))
|
||||
else
|
||||
CursorList.emptyList()
|
||||
}
|
||||
|
||||
private fun queryMessages(query: String, threadId: Long): CursorList<MessageResult?> {
|
||||
val messages = searchDatabase.queryMessages(query, threadId)
|
||||
return if (messages != null)
|
||||
CursorList(messages, MessageModelBuilder(context))
|
||||
else
|
||||
CursorList.emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfortunately [DatabaseUtils.sqlEscapeString] is not sufficient for our purposes.
|
||||
* MATCH queries have a separate format of their own that disallow most "special" characters.
|
||||
*
|
||||
* Also, SQLite can't search for apostrophes, meaning we can't normally find words like "I'm".
|
||||
* However, if we replace the apostrophe with a space, then the query will find the match.
|
||||
*/
|
||||
private fun sanitizeQuery(query: String): String {
|
||||
val out = StringBuilder()
|
||||
|
||||
for (i in 0..<query.length) {
|
||||
val c = query[i]
|
||||
if (!BANNED_CHARACTERS.contains(c)) {
|
||||
out.append(c)
|
||||
} else if (c == '\'') {
|
||||
out.append(' ')
|
||||
}
|
||||
}
|
||||
|
||||
return out.toString()
|
||||
}
|
||||
|
||||
private class ContactModelBuilder(
|
||||
private val contactDb: SessionContactDatabase,
|
||||
private val threadDb: ThreadDatabase
|
||||
) : CursorList.ModelBuilder<Contact> {
|
||||
override fun build(cursor: Cursor): Contact {
|
||||
val threadRecord = threadDb.readerFor(cursor).current
|
||||
var contact =
|
||||
contactDb.getContactWithAccountID(threadRecord.recipient.address.toString())
|
||||
if (contact == null) {
|
||||
contact = Contact(threadRecord.recipient.address.toString())
|
||||
contact.threadID = threadRecord.threadId
|
||||
}
|
||||
return contact
|
||||
}
|
||||
}
|
||||
|
||||
private class GroupModelBuilder(
|
||||
private val threadDatabase: ThreadDatabase,
|
||||
private val groupDatabase: GroupDatabase
|
||||
) : CursorList.ModelBuilder<GroupRecord> {
|
||||
override fun build(cursor: Cursor): GroupRecord {
|
||||
val threadRecord = threadDatabase.readerFor(cursor).current
|
||||
return groupDatabase.getGroup(threadRecord.recipient.address.toGroupString()).get()
|
||||
}
|
||||
}
|
||||
|
||||
private class MessageModelBuilder(private val context: Context) : CursorList.ModelBuilder<MessageResult> {
|
||||
override fun build(cursor: Cursor): MessageResult {
|
||||
val conversationAddress =
|
||||
fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.CONVERSATION_ADDRESS)))
|
||||
val messageAddress =
|
||||
fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.MESSAGE_ADDRESS)))
|
||||
val conversationRecipient = Recipient.from(context, conversationAddress, false)
|
||||
val messageRecipient = Recipient.from(context, messageAddress, false)
|
||||
val body = cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.SNIPPET))
|
||||
val sentMs =
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_SENT))
|
||||
val threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.THREAD_ID))
|
||||
|
||||
return MessageResult(conversationRecipient, messageRecipient, body, threadId, sentMs)
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback<E> {
|
||||
fun onResult(result: E)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG: String = SearchRepository::class.java.simpleName
|
||||
|
||||
private val BANNED_CHARACTERS: MutableSet<Char> = HashSet()
|
||||
|
||||
init {
|
||||
// Construct a list containing several ranges of invalid ASCII characters
|
||||
// See: https://www.ascii-code.com/
|
||||
for (i in 33..47) {
|
||||
BANNED_CHARACTERS.add(i.toChar())
|
||||
} // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /
|
||||
|
||||
for (i in 58..64) {
|
||||
BANNED_CHARACTERS.add(i.toChar())
|
||||
} // :, ;, <, =, >, ?, @
|
||||
|
||||
for (i in 91..96) {
|
||||
BANNED_CHARACTERS.add(i.toChar())
|
||||
} // [, \, ], ^, _, `
|
||||
|
||||
for (i in 123..126) {
|
||||
BANNED_CHARACTERS.add(i.toChar())
|
||||
} // {, |, }, ~
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue