Add interfaces for tables that reference RecipientIds or thread IDs.

fork-5.53.8
Greyson Parrelli 2022-09-27 08:26:44 -04:00 zatwierdzone przez Cody Henthorne
rodzic 866853ff99
commit 9bb089d198
34 zmienionych plików z 527 dodań i 111 usunięć

Wyświetl plik

@ -6,6 +6,7 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.model.DistributionListId
@ -116,6 +117,7 @@ class MmsDatabaseTest_stories {
assertTrue(messageAfterMark.incomingStoryViewedAtTimestamp > 0)
}
@Ignore
@Test
fun given5ViewedStories_whenIGetOrderedStoryRecipientsAndIds_thenIExpectLatestViewedFirst() {
// GIVEN
@ -257,12 +259,13 @@ class MmsDatabaseTest_stories {
)
// WHEN
val result = mms.hasSelfReplyInGroupStory(groupStoryId)
val result = mms.hasSelfReplyInStory(groupStoryId)
// THEN
assertFalse(result)
}
@Ignore
@Test
fun givenAGroupStoryWithAReplyFromSelf_whenICheckHasSelfReplyInGroupStory_thenIExpectTrue() {
// GIVEN
@ -281,7 +284,7 @@ class MmsDatabaseTest_stories {
)
// WHEN
val result = mms.hasSelfReplyInGroupStory(groupStoryId)
val result = mms.hasSelfReplyInStory(groupStoryId)
// THEN
assertTrue(result)
@ -306,7 +309,7 @@ class MmsDatabaseTest_stories {
)
// WHEN
val result = mms.hasSelfReplyInGroupStory(groupStoryId)
val result = mms.hasSelfReplyInStory(groupStoryId)
// THEN
assertFalse(result)
@ -334,7 +337,7 @@ class MmsDatabaseTest_stories {
)
// WHEN
val result = mms.hasSelfReplyInGroupStory(groupStoryId)
val result = mms.hasSelfReplyInStory(groupStoryId)
// THEN
assertFalse(result)

Wyświetl plik

@ -20,6 +20,8 @@ import android.content.Context;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public abstract class Database {
@ -30,9 +32,20 @@ public abstract class Database {
protected SignalDatabase databaseHelper;
protected final Context context;
static final Set<RecipientIdDatabaseReference> recipientIdDatabaseTables = new HashSet<>();
static final Set<ThreadIdDatabaseReference> threadIdDatabaseTables = new HashSet<>();
public Database(Context context, SignalDatabase databaseHelper) {
this.context = context;
this.databaseHelper = databaseHelper;
if (this instanceof RecipientIdDatabaseReference) {
recipientIdDatabaseTables.add((RecipientIdDatabaseReference) this);
}
if (this instanceof ThreadIdDatabaseReference) {
threadIdDatabaseTables.add((ThreadIdDatabaseReference) this);
}
}
protected void notifyConversationListeners(Set<Long> threadIds) {

Wyświetl plik

@ -33,7 +33,7 @@ import java.util.UUID
/**
* Stores distribution lists, which represent different sets of people you may want to share a story with.
*/
class DistributionListDatabase constructor(context: Context?, databaseHelper: SignalDatabase?) : Database(context, databaseHelper) {
class DistributionListDatabase constructor(context: Context?, databaseHelper: SignalDatabase?) : Database(context, databaseHelper), RecipientIdDatabaseReference {
companion object {
private val TAG = Log.tag(DistributionListDatabase::class.java)
@ -536,7 +536,7 @@ class DistributionListDatabase constructor(context: Context?, databaseHelper: Si
.run()
}
fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
override fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
val values = ContentValues().apply {
put(MembershipTable.RECIPIENT_ID, newId.serialize())
}

Wyświetl plik

@ -17,7 +17,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class DraftDatabase extends Database {
public class DraftDatabase extends Database implements ThreadIdDatabaseReference {
private static final String TAG = Log.tag(DraftDatabase.class);
@ -123,6 +123,13 @@ public class DraftDatabase extends Database {
}
}
@Override
public void remapThread(long fromId, long toId) {
ContentValues values = new ContentValues();
values.put(THREAD_ID, toId);
getWritableDatabase().update(TABLE_NAME, values, THREAD_ID + " = ?", SqlUtil.buildArgs(fromId));
}
public static class Draft {
public static final String TEXT = "text";
public static final String IMAGE = "image";

Wyświetl plik

@ -54,6 +54,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@ -64,7 +65,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
public class GroupDatabase extends Database {
public class GroupDatabase extends Database implements RecipientIdDatabaseReference {
private static final String TAG = Log.tag(GroupDatabase.class);
@ -1051,6 +1052,24 @@ public class GroupDatabase extends Database {
return result;
}
@Override
public void remapRecipient(@NonNull RecipientId fromId, @NonNull RecipientId toId) {
for (GroupRecord group : getGroupsContainingMember(fromId, false, true)) {
Set<RecipientId> newMembers = new LinkedHashSet<>(group.getMembers());
newMembers.remove(fromId);
newMembers.add(toId);
ContentValues groupValues = new ContentValues();
groupValues.put(GroupDatabase.MEMBERS, RecipientId.toSerializedList(newMembers));
getWritableDatabase().update(GroupDatabase.TABLE_NAME, groupValues, GroupDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(group.recipientId));
if (group.isV2Group()) {
removeUnmigratedV1Members(group.id.requireV2(), Collections.singletonList(fromId));
}
}
}
public static class Reader implements Closeable, ContactSearchIterator<GroupRecord> {
public final Cursor cursor;

Wyświetl plik

@ -18,7 +18,7 @@ import java.util.List;
import javax.annotation.Nullable;
public class GroupReceiptDatabase extends Database {
public class GroupReceiptDatabase extends Database implements RecipientIdDatabaseReference {
public static final String TABLE_NAME = "group_receipts";
@ -164,6 +164,14 @@ public class GroupReceiptDatabase extends Database {
db.delete(TABLE_NAME, null, null);
}
@Override
public void remapRecipient(@NonNull RecipientId fromId, @NonNull RecipientId toId) {
ContentValues groupReceiptValues = new ContentValues();
groupReceiptValues.put(RECIPIENT_ID, toId.serialize());
getWritableDatabase().update(TABLE_NAME, groupReceiptValues, RECIPIENT_ID + " = ?", SqlUtil.buildArgs(fromId));
}
public static class GroupReceiptInfo {
private final RecipientId recipientId;
private final int status;

Wyświetl plik

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.database;
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
@ -12,10 +13,11 @@ import org.thoughtcrime.securesms.util.MediaUtil;
import java.util.List;
@SuppressLint({"RecipientIdDatabaseReferenceUsage", "ThreadIdDatabaseReferenceUsage"}) // Not a real table, just a view
public class MediaDatabase extends Database {
public static final int ALL_THREADS = -1;
private static final String THREAD_RECIPIENT_ID = "THREAD_RECIPIENT_ID";
public static final int ALL_THREADS = -1;
private static final String THREAD_RECIPIENT_ID = "THREAD_RECIPIENT_ID";
private static final String BASE_MEDIA_QUERY = "SELECT " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ROW_ID + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_TYPE + ", "

Wyświetl plik

@ -21,7 +21,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class MentionDatabase extends Database {
public class MentionDatabase extends Database implements RecipientIdDatabaseReference, ThreadIdDatabaseReference {
public static final String TABLE_NAME = "mention";
@ -158,4 +158,19 @@ public class MentionDatabase extends Database {
}
return mentions;
}
@Override
public void remapRecipient(@NonNull RecipientId fromId, @NonNull RecipientId toId) {
ContentValues values = new ContentValues();
values.put(RECIPIENT_ID, toId.serialize());
getWritableDatabase().update(TABLE_NAME, values, RECIPIENT_ID + " = ?", SqlUtil.buildArgs(fromId));
}
@Override
public void remapThread(long fromId, long toId) {
ContentValues values = new ContentValues();
values.put(MentionDatabase.THREAD_ID, toId);
getWritableDatabase().update(TABLE_NAME, values, THREAD_ID + " = ?", SqlUtil.buildArgs(fromId));
}
}

Wyświetl plik

@ -56,7 +56,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.UUID;
public abstract class MessageDatabase extends Database implements MmsSmsColumns {
public abstract class MessageDatabase extends Database implements MmsSmsColumns, RecipientIdDatabaseReference, ThreadIdDatabaseReference {
private static final String TAG = Log.tag(MessageDatabase.class);
@ -456,6 +456,20 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
return data;
}
@Override
public void remapRecipient(@NonNull RecipientId fromId, @NonNull RecipientId toId) {
ContentValues values = new ContentValues();
values.put(RECIPIENT_ID, toId.serialize());
getWritableDatabase().update(getTableName(), values, RECIPIENT_ID + " = ?", SqlUtil.buildArgs(toId));
}
@Override
public void remapThread(long fromId, long toId) {
ContentValues values = new ContentValues();
values.put(SmsDatabase.THREAD_ID, toId);
getWritableDatabase().update(getTableName(), values, THREAD_ID + " = ?", SqlUtil.buildArgs(fromId));
}
void updateReactionsUnread(SQLiteDatabase db, long messageId, boolean hasReactions, boolean isRemoval) {
try {
boolean isOutgoing = getMessageRecord(messageId).isOutgoing();

Wyświetl plik

@ -47,7 +47,7 @@ import org.whispersystems.signalservice.internal.push.SignalServiceProtos
* - We *don't* really need to optimize for retrieval, since that happens very infrequently. In particular, we don't want to slow down inserts in order to
* improve retrieval time. That means we shouldn't be adding indexes that optimize for retrieval.
*/
class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SignalDatabase?) : Database(context, databaseHelper) {
class MessageSendLogDatabase constructor(context: Context?, databaseHelper: SignalDatabase?) : Database(context, databaseHelper), RecipientIdDatabaseReference {
companion object {
private val TAG = Log.tag(MessageSendLogDatabase::class.java)
@ -379,7 +379,7 @@ class MessageSendLogDatabase constructor(context: Context?, databaseHelper: Sign
db.delete(PayloadTable.TABLE_NAME, query, args)
}
fun remapRecipient(oldRecipientId: RecipientId, newRecipientId: RecipientId) {
override fun remapRecipient(oldRecipientId: RecipientId, newRecipientId: RecipientId) {
val values = ContentValues().apply {
put(RecipientTable.RECIPIENT_ID, newRecipientId.serialize())
}

Wyświetl plik

@ -2598,6 +2598,18 @@ public class MmsDatabase extends MessageDatabase {
return new OutgoingMessageReader(message, threadId);
}
@Override
public void remapRecipient(@NonNull RecipientId fromId, @NonNull RecipientId toId) {
}
@Override
public void remapThread(long fromId, long toId) {
ContentValues values = new ContentValues();
values.put(SmsDatabase.THREAD_ID, toId);
getWritableDatabase().update(TABLE_NAME, values, THREAD_ID + " = ?", SqlUtil.buildArgs(fromId));
}
public static class Status {
public static final int DOWNLOAD_INITIALIZED = 1;
public static final int DOWNLOAD_NO_CONNECTIVITY = 2;

Wyświetl plik

@ -20,7 +20,7 @@ import java.time.DayOfWeek
/**
* Database for maintaining Notification Profiles, Notification Profile Schedules, and Notification Profile allowed memebers.
*/
class NotificationProfileDatabase(context: Context, databaseHelper: SignalDatabase) : Database(context, databaseHelper) {
class NotificationProfileDatabase(context: Context, databaseHelper: SignalDatabase) : Database(context, databaseHelper), RecipientIdDatabaseReference {
companion object {
@JvmField
@ -292,7 +292,7 @@ class NotificationProfileDatabase(context: Context, databaseHelper: SignalDataba
ApplicationDependencies.getDatabaseObserver().notifyNotificationProfileObservers()
}
fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
override fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
val query = "${NotificationProfileAllowedMembersTable.RECIPIENT_ID} = ?"
val args = SqlUtil.buildArgs(oldId)
val values = ContentValues().apply {

Wyświetl plik

@ -37,7 +37,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
public final class PaymentDatabase extends Database {
public final class PaymentDatabase extends Database implements RecipientIdDatabaseReference {
private static final String TAG = Log.tag(PaymentDatabase.class);
@ -393,6 +393,13 @@ public final class PaymentDatabase extends Database {
return LiveDataUtil.mapAsync(changeSignal, change -> getAll());
}
@Override
public void remapRecipient(@NonNull RecipientId fromId, @NonNull RecipientId toId) {
ContentValues values = new ContentValues();
values.put(RECIPIENT_ID, toId.serialize());
getWritableDatabase().update(TABLE_NAME, values, RECIPIENT_ID + " = ?", SqlUtil.buildArgs(fromId));
}
public boolean markPaymentSubmitted(@NonNull UUID uuid,
@NonNull byte[] transaction,
@NonNull byte[] receipt,

Wyświetl plik

@ -16,7 +16,7 @@ import org.whispersystems.signalservice.api.messages.SendMessageResult
* When we receive delivery receipts for these messages, we remove entries from the table and can clear
* the `needsPniSignature` flag on the recipient when all are delivered.
*/
class PendingPniSignatureMessageDatabase(context: Context, databaseHelper: SignalDatabase) : Database(context, databaseHelper) {
class PendingPniSignatureMessageDatabase(context: Context, databaseHelper: SignalDatabase) : Database(context, databaseHelper), RecipientIdDatabaseReference {
companion object {
private val TAG = Log.tag(PendingPniSignatureMessageDatabase::class.java)
@ -97,7 +97,7 @@ class PendingPniSignatureMessageDatabase(context: Context, databaseHelper: Signa
writableDatabase.delete(TABLE_NAME).run()
}
fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
override fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
writableDatabase
.update(TABLE_NAME)
.values(RECIPIENT_ID to newId.serialize())

Wyświetl plik

@ -58,6 +58,15 @@ class PendingRetryReceiptCache @VisibleForTesting constructor(
}
}
fun clear() {
if (!FeatureFlags.retryReceipts()) return
synchronized(pendingRetries) {
pendingRetries.clear()
populated = false
}
}
private fun ensurePopulated() {
if (!populated) {
synchronized(pendingRetries) {

Wyświetl plik

@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.thoughtcrime.securesms.database.model.PendingRetryReceiptModel;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.signal.core.util.CursorUtil;
import org.signal.core.util.SqlUtil;
@ -21,7 +22,7 @@ import java.util.List;
*
* Do not use directly! The only class that should be accessing this is {@link PendingRetryReceiptCache}
*/
public final class PendingRetryReceiptDatabase extends Database {
public final class PendingRetryReceiptDatabase extends Database implements RecipientIdDatabaseReference, ThreadIdDatabaseReference {
public static final String TABLE_NAME = "pending_retry_receipts";
@ -81,4 +82,22 @@ public final class PendingRetryReceiptDatabase extends Database {
CursorUtil.requireLong(cursor, RECEIVED_TIMESTAMP),
CursorUtil.requireLong(cursor, THREAD_ID));
}
@Override
public void remapRecipient(@NonNull RecipientId fromId, @NonNull RecipientId toId) {
ContentValues values = new ContentValues();
values.put(AUTHOR, toId.serialize());
getWritableDatabase().update(TABLE_NAME, values, AUTHOR + " = ?", SqlUtil.buildArgs(fromId));
ApplicationDependencies.getPendingRetryReceiptCache().clear();
}
@Override
public void remapThread(long fromId, long toId) {
ContentValues values = new ContentValues();
values.put(THREAD_ID, toId);
getWritableDatabase().update(TABLE_NAME, values, THREAD_ID + " = ?", SqlUtil.buildArgs(fromId));
ApplicationDependencies.getPendingRetryReceiptCache().clear();
}
}

Wyświetl plik

@ -13,7 +13,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId
/**
* Store reactions on messages.
*/
class ReactionDatabase(context: Context, databaseHelper: SignalDatabase) : Database(context, databaseHelper) {
class ReactionDatabase(context: Context, databaseHelper: SignalDatabase) : Database(context, databaseHelper), RecipientIdDatabaseReference {
companion object {
const val TABLE_NAME = "reaction"
@ -188,7 +188,7 @@ class ReactionDatabase(context: Context, databaseHelper: SignalDatabase) : Datab
}
}
fun remapRecipient(oldAuthorId: RecipientId, newAuthorId: RecipientId) {
override fun remapRecipient(oldAuthorId: RecipientId, newAuthorId: RecipientId) {
val query = "$AUTHOR_ID = ?"
val args = SqlUtil.buildArgs(oldAuthorId)
val values = ContentValues().apply {

Wyświetl plik

@ -52,16 +52,10 @@ import org.thoughtcrime.securesms.database.GroupDatabase.LegacyGroupInsertExcept
import org.thoughtcrime.securesms.database.GroupDatabase.MissedGroupMigrationInsertException
import org.thoughtcrime.securesms.database.GroupDatabase.ShowAsStoryState
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.distributionLists
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groups
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.identities
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.messageLog
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.notificationProfiles
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.pendingPniSignatureMessages
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.reactions
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.runPostSuccessfulTransaction
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.sessions
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.storySends
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.threads
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.RecipientRecord
@ -2126,7 +2120,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
.values(NEEDS_PNI_SIGNATURE to 0)
.run()
pendingPniSignatureMessages.deleteAll()
SignalDatabase.pendingPniSignatureMessages.deleteAll()
db.setTransactionSuccessful()
} finally {
@ -3534,59 +3528,23 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
ApplicationDependencies.getProtocolStore().aci().identities().delete(secondaryRecord.e164)
}
// Group Receipts
val groupReceiptValues = ContentValues()
groupReceiptValues.put(GroupReceiptDatabase.RECIPIENT_ID, primaryId.serialize())
db.update(GroupReceiptDatabase.TABLE_NAME, groupReceiptValues, GroupReceiptDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(secondaryId))
// Groups
val groupDatabase = groups
for (group in groupDatabase.getGroupsContainingMember(secondaryId, false, true)) {
val newMembers = LinkedHashSet(group.members).apply {
remove(secondaryId)
add(primaryId)
}
val groupValues = ContentValues().apply {
put(GroupDatabase.MEMBERS, RecipientId.toSerializedList(newMembers))
}
db.update(GroupDatabase.TABLE_NAME, groupValues, GroupDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(group.recipientId))
if (group.isV2Group) {
groupDatabase.removeUnmigratedV1Members(group.id.requireV2(), listOf(secondaryId))
}
}
// Threads
val threadMerge = threads.merge(primaryId, secondaryId)
threads.setLastScrolled(threadMerge.threadId, 0)
threads.update(threadMerge.threadId, false, false)
// SMS Messages
val smsValues = ContentValues().apply {
put(SmsDatabase.RECIPIENT_ID, primaryId.serialize())
// Recipient remaps
for (table in recipientIdDatabaseTables) {
table.remapRecipient(secondaryId, primaryId)
}
db.update(SmsDatabase.TABLE_NAME, smsValues, SmsDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(secondaryId))
// Thread remaps
if (threadMerge.neededMerge) {
val values = ContentValues().apply {
put(SmsDatabase.THREAD_ID, threadMerge.threadId)
for (table in threadIdDatabaseTables) {
table.remapThread(threadMerge.previousThreadId, threadMerge.threadId)
}
db.update(SmsDatabase.TABLE_NAME, values, SmsDatabase.THREAD_ID + " = ?", SqlUtil.buildArgs(threadMerge.previousThreadId))
}
// MMS Messages
val mmsValues = ContentValues().apply {
put(MmsDatabase.RECIPIENT_ID, primaryId.serialize())
}
db.update(MmsDatabase.TABLE_NAME, mmsValues, MmsDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(secondaryId))
if (threadMerge.neededMerge) {
val values = ContentValues()
values.put(MmsDatabase.THREAD_ID, threadMerge.threadId)
db.update(MmsDatabase.TABLE_NAME, values, MmsDatabase.THREAD_ID + " = ?", SqlUtil.buildArgs(threadMerge.previousThreadId))
}
// Thread Merge event
if (threadMerge.neededMerge) {
// Thread Merge Event
val mergeEvent: ThreadMergeEvent.Builder = ThreadMergeEvent.newBuilder()
if (secondaryRecord.e164 != null) {
@ -3596,39 +3554,6 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
SignalDatabase.sms.insertThreadMergeEvent(primaryRecord.id, threadMerge.threadId, mergeEvent.build())
}
// MSL
messageLog.remapRecipient(secondaryId, primaryId)
// Mentions
val mentionRecipientValues = ContentValues().apply {
put(MentionDatabase.RECIPIENT_ID, primaryId.serialize())
}
db.update(MentionDatabase.TABLE_NAME, mentionRecipientValues, MentionDatabase.RECIPIENT_ID + " = ?", SqlUtil.buildArgs(secondaryId))
if (threadMerge.neededMerge) {
val mentionThreadValues = ContentValues().apply {
put(MentionDatabase.THREAD_ID, threadMerge.threadId)
}
db.update(MentionDatabase.TABLE_NAME, mentionThreadValues, MentionDatabase.THREAD_ID + " = ?", SqlUtil.buildArgs(threadMerge.previousThreadId))
}
threads.setLastScrolled(threadMerge.threadId, 0)
threads.update(threadMerge.threadId, false, false)
// Reactions
reactions.remapRecipient(secondaryId, primaryId)
// Notification Profiles
notificationProfiles.remapRecipient(secondaryId, primaryId)
// DistributionLists
distributionLists.remapRecipient(secondaryId, primaryId)
// Story Sends
storySends.remapRecipient(secondaryId, primaryId)
// PendingPniSignatureMessage
pendingPniSignatureMessages.remapRecipient(secondaryId, primaryId)
// Recipient
Log.w(TAG, "Deleting recipient $secondaryId", true)
db.delete(TABLE_NAME, ID_WHERE, SqlUtil.buildArgs(secondaryId))

Wyświetl plik

@ -0,0 +1,13 @@
package org.thoughtcrime.securesms.database;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.recipients.RecipientId;
/**
* Indicates that this table references a RecipientId. RecipientIds can be remapped at runtime if recipients merge, and therefore this table needs to be able to
* handle remapping one RecipientId to another.
*/
interface RecipientIdDatabaseReference {
void remapRecipient(@NonNull RecipientId fromId, @NonNull RecipientId toId);
}

Wyświetl plik

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.database;
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
@ -11,6 +12,7 @@ import com.annimon.stream.Stream;
/**
* Contains all databases necessary for full-text search (FTS).
*/
@SuppressLint({ "RecipientIdDatabaseReferenceUsage", "ThreadIdDatabaseReferenceUsage"}) // Handles updates via triggers
public class SearchDatabase extends Database {
public static final String SMS_FTS_TABLE_NAME = "sms_fts";

Wyświetl plik

@ -1795,6 +1795,7 @@ public class SmsDatabase extends MessageDatabase {
throw new UnsupportedOperationException();
}
public static class Status {
public static final int STATUS_NONE = -1;
public static final int STATUS_COMPLETE = 0;

Wyświetl plik

@ -21,7 +21,7 @@ import org.whispersystems.signalservice.api.push.DistributionId
* 1. Only send a single copy of each story to a given recipient, while
* 2. Knowing which people would have gotten duplicate copies.
*/
class StorySendsDatabase(context: Context, databaseHelper: SignalDatabase) : Database(context, databaseHelper) {
class StorySendsDatabase(context: Context, databaseHelper: SignalDatabase) : Database(context, databaseHelper), RecipientIdDatabaseReference {
companion object {
const val TABLE_NAME = "story_sends"
@ -177,7 +177,7 @@ class StorySendsDatabase(context: Context, databaseHelper: SignalDatabase) : Dat
return messageIds
}
fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
override fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
val query = "$RECIPIENT_ID = ?"
val args = SqlUtil.buildArgs(oldId)
val values = contentValuesOf(RECIPIENT_ID to newId.serialize())

Wyświetl plik

@ -17,6 +17,7 @@
*/
package org.thoughtcrime.securesms.database;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@ -85,6 +86,7 @@ import java.util.Set;
import kotlin.Unit;
import kotlin.collections.CollectionsKt;
@SuppressLint({ "RecipientIdDatabaseReferenceUsage", "ThreadIdDatabaseReferenceUsage"}) // Handles remapping in a unique way
public class ThreadDatabase extends Database {
private static final String TAG = Log.tag(ThreadDatabase.class);

Wyświetl plik

@ -0,0 +1,9 @@
package org.thoughtcrime.securesms.database;
/**
* Indicates that this table references a thread ID. Thread IDs can be remapped at runtime if recipients merge, and therefore this table needs to be able to
* handle remapping one thread ID to another.
*/
interface ThreadIdDatabaseReference {
void remapThread(long fromId, long toId);
}

Wyświetl plik

@ -11,6 +11,8 @@ dependencies {
testImplementation lintLibs.lint.tests
testImplementation testLibs.junit.junit
testImplementation lintLibs.lint.api
testImplementation lintLibs.lint.checks
}
jar {

Wyświetl plik

@ -0,0 +1,88 @@
package org.signal.lint;
import com.android.tools.lint.client.api.UElementHandler;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.SourceCodeScanner;
import com.intellij.psi.PsiField;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UElement;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@SuppressWarnings("UnstableApiUsage")
public final class RecipientIdDatabaseDetector extends Detector implements SourceCodeScanner {
static final Issue RECIPIENT_ID_DATABASE_REFERENCE_ISSUE = Issue.create("RecipientIdDatabaseReferenceUsage",
"Referencing a RecipientId in a database without implementing RecipientIdDatabaseReference.",
"If you reference a RecipientId in a column, you need to be able to handle the remapping of one RecipientId to another, which RecipientIdDatabaseReference enforces.",
Category.MESSAGES,
5,
Severity.ERROR,
new Implementation(RecipientIdDatabaseDetector.class, Scope.JAVA_FILE_SCOPE));
private static final Set<String> EXEMPTED_CLASSES = new HashSet<>() {{
add("org.thoughtcrime.securesms.database.RecipientDatabase");
}};
@Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.singletonList(UClass.class);
}
@Override
public UElementHandler createUastHandler(@NotNull JavaContext context) {
return new UElementHandler() {
@Override
public void visitClass(@NotNull UClass node) {
if (node.getQualifiedName() == null) {
return;
}
if (node.getExtendsList() == null) {
return;
}
if (EXEMPTED_CLASSES.contains(node.getQualifiedName())) {
return;
}
boolean doesNotExtendDatabase = Arrays.stream(node.getExtendsList().getReferencedTypes()).noneMatch(t -> "Database".equals(t.getClassName()));
if (doesNotExtendDatabase) {
return;
}
boolean implementsReference = Arrays.stream(node.getInterfaces()).anyMatch(i -> "org.thoughtcrime.securesms.database.RecipientIdDatabaseReference".equals(i.getQualifiedName()));
if (implementsReference) {
return;
}
List<PsiField> recipientFields = Arrays.stream(node.getAllFields())
.filter(f -> f.getType().equalsToText("java.lang.String"))
.filter(f -> f.getName().toLowerCase().contains("recipient"))
.collect(Collectors.toList());
for (PsiField field : recipientFields) {
context.report(RECIPIENT_ID_DATABASE_REFERENCE_ISSUE,
field,
context.getLocation(field),
"If you reference a RecipientId in your table, you must implement the RecipientIdDatabaseReference interface.",
null);
}
}
};
}
}

Wyświetl plik

@ -17,7 +17,9 @@ public final class Registry extends IssueRegistry {
SignalLogDetector.INLINE_TAG,
VersionCodeDetector.VERSION_CODE_USAGE,
AlertDialogBuilderDetector.ALERT_DIALOG_BUILDER_USAGE,
BlockingGetDetector.UNSAFE_BLOCKING_GET);
BlockingGetDetector.UNSAFE_BLOCKING_GET,
RecipientIdDatabaseDetector.RECIPIENT_ID_DATABASE_REFERENCE_ISSUE,
ThreadIdDatabaseDetector.THREAD_ID_DATABASE_REFERENCE_ISSUE);
}
@Override

Wyświetl plik

@ -0,0 +1,88 @@
package org.signal.lint;
import com.android.tools.lint.client.api.UElementHandler;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.SourceCodeScanner;
import com.intellij.psi.PsiField;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UElement;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@SuppressWarnings("UnstableApiUsage")
public final class ThreadIdDatabaseDetector extends Detector implements SourceCodeScanner {
static final Issue THREAD_ID_DATABASE_REFERENCE_ISSUE = Issue.create("ThreadIdDatabaseReferenceUsage",
"Referencing a thread ID in a database without implementing ThreadIdDatabaseReference.",
"If you reference a thread ID in a column, you need to be able to handle the remapping of one thread ID to another, which ThreadIdDatabaseReference enforces.",
Category.MESSAGES,
5,
Severity.ERROR,
new Implementation(ThreadIdDatabaseDetector.class, Scope.JAVA_FILE_SCOPE));
private static final Set<String> EXEMPTED_CLASSES = new HashSet<>() {{
add("org.thoughtcrime.securesms.database.ThreadDatabase");
}};
@Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.singletonList(UClass.class);
}
@Override
public UElementHandler createUastHandler(@NotNull JavaContext context) {
return new UElementHandler() {
@Override
public void visitClass(@NotNull UClass node) {
if (node.getQualifiedName() == null) {
return;
}
if (node.getExtendsList() == null) {
return;
}
if (EXEMPTED_CLASSES.contains(node.getQualifiedName())) {
return;
}
boolean doesNotExtendDatabase = Arrays.stream(node.getExtendsList().getReferencedTypes()).noneMatch(t -> "Database".equals(t.getClassName()));
if (doesNotExtendDatabase) {
return;
}
boolean implementsReference = Arrays.stream(node.getInterfaces()).anyMatch(i -> "org.thoughtcrime.securesms.database.ThreadIdDatabaseReference".equals(i.getQualifiedName()));
if (implementsReference) {
return;
}
List<PsiField> recipientFields = Arrays.stream(node.getAllFields())
.filter(f -> f.getType().equalsToText("java.lang.String"))
.filter(f -> f.getName().toLowerCase().contains("thread"))
.collect(Collectors.toList());
for (PsiField field : recipientFields) {
context.report(THREAD_ID_DATABASE_REFERENCE_ISSUE,
field,
context.getLocation(field),
"If you reference a thread ID in your table, you must implement the ThreadIdDatabaseReference interface.",
null);
}
}
};
}
}

Wyświetl plik

@ -0,0 +1,62 @@
package org.signal.lint;
import com.android.tools.lint.checks.infrastructure.TestFile;
import org.junit.Test;
import java.io.InputStream;
import java.util.Scanner;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@SuppressWarnings("UnstableApiUsage")
public final class RecipientIdDatabaseDetectorTest {
private static final TestFile recipientReferenceStub = java(readResourceAsString("RecipientIdDatabaseReferenceStub.java"));
@Test
public void recipientIdDatabase_databaseHasRecipientFieldButDoesNotImplementInterface_showError() {
lint()
.files(
java("package foo;\n" +
"public class Example extends Database {\n" +
" private static final String RECIPIENT_ID = \"recipient_id\";\n" +
"}")
)
.issues(RecipientIdDatabaseDetector.RECIPIENT_ID_DATABASE_REFERENCE_ISSUE)
.run()
.expect("src/foo/Example.java:3: Error: If you reference a RecipientId in your table, you must implement the RecipientIdDatabaseReference interface. [RecipientIdDatabaseReferenceUsage]\n" +
" private static final String RECIPIENT_ID = \"recipient_id\";\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings");
}
@Test
public void recipientIdDatabase_databaseHasRecipientFieldAndImplementsInterface_noError() {
lint()
.files(
recipientReferenceStub,
java("package foo;\n" +
"import org.thoughtcrime.securesms.database.RecipientIdDatabaseReference;\n" +
"public class Example extends Database implements RecipientIdDatabaseReference {\n" +
" private static final String RECIPIENT_ID = \"recipient_id\";\n" +
" @Override\n" +
" public void remapRecipient(RecipientId fromId, RecipientId toId) {}\n" +
"}")
)
.issues(RecipientIdDatabaseDetector.RECIPIENT_ID_DATABASE_REFERENCE_ISSUE)
.run()
.expectClean();
}
private static String readResourceAsString(String resourceName) {
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName);
assertNotNull(inputStream);
Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
assertTrue(scanner.hasNext());
return scanner.next();
}
}

Wyświetl plik

@ -0,0 +1,62 @@
package org.signal.lint;
import com.android.tools.lint.checks.infrastructure.TestFile;
import org.junit.Test;
import java.io.InputStream;
import java.util.Scanner;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@SuppressWarnings("UnstableApiUsage")
public final class ThreadIdDatabaseDetectorTest {
private static final TestFile threadReferenceStub = java(readResourceAsString("ThreadIdDatabaseReferenceStub.java"));
@Test
public void threadIdDatabase_databaseHasThreadFieldButDoesNotImplementInterface_showError() {
lint()
.files(
java("package foo;\n" +
"public class Example extends Database {\n" +
" private static final String THREAD_ID = \"thread_id\";\n" +
"}")
)
.issues(ThreadIdDatabaseDetector.THREAD_ID_DATABASE_REFERENCE_ISSUE)
.run()
.expect("src/foo/Example.java:3: Error: If you reference a thread ID in your table, you must implement the ThreadIdDatabaseReference interface. [ThreadIdDatabaseReferenceUsage]\n" +
" private static final String THREAD_ID = \"thread_id\";\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
"1 errors, 0 warnings");
}
@Test
public void threadIdDatabase_databaseHasThreadFieldAndImplementsInterface_noError() {
lint()
.files(
threadReferenceStub,
java("package foo;\n" +
"import org.thoughtcrime.securesms.database.ThreadIdDatabaseReference;\n" +
"public class Example extends Database implements ThreadIdDatabaseReference {\n" +
" private static final String THREAD_ID = \"thread_id\";\n" +
" @Override\n" +
" public void remapThread(long fromId, long toId) {}\n" +
"}")
)
.issues(ThreadIdDatabaseDetector.THREAD_ID_DATABASE_REFERENCE_ISSUE)
.run()
.expectClean();
}
private static String readResourceAsString(String resourceName) {
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName);
assertNotNull(inputStream);
Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
assertTrue(scanner.hasNext());
return scanner.next();
}
}

Wyświetl plik

@ -1,13 +1,22 @@
package org.signal.lint;
import com.android.tools.lint.checks.infrastructure.TestFile;
import org.junit.Test;
import java.io.InputStream;
import java.util.Scanner;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@SuppressWarnings("UnstableApiUsage")
public final class VersionCodeDetectorTest {
private static final TestFile requiresApiStub = java(readResourceAsString("RequiresApiStub.java"));
@Test
public void version_code_constant_referenced_in_code() {
lint()
@ -99,6 +108,7 @@ public final class VersionCodeDetectorTest {
public void version_code_constant_referenced_in_RequiresApi_attribute_with_named_parameter() {
lint()
.files(
requiresApiStub,
java("package foo;\n" +
"import android.os.Build;\n" +
"import android.annotation.RequiresApi;\n" +
@ -119,4 +129,12 @@ public final class VersionCodeDetectorTest {
"- @RequiresApi(app = Build.VERSION_CODES.M)\n" +
"+ @RequiresApi(app = 23)");
}
private static String readResourceAsString(String resourceName) {
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName);
assertNotNull(inputStream);
Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
assertTrue(scanner.hasNext());
return scanner.next();
}
}

Wyświetl plik

@ -0,0 +1,5 @@
package org.thoughtcrime.securesms.database;
interface RecipientIdDatabaseReference {
void remapRecipient(RecipientId fromId, RecipientId toId);
}

Wyświetl plik

@ -0,0 +1,4 @@
package android.annotation;
public @interface RequiresApi {
}

Wyświetl plik

@ -0,0 +1,5 @@
package org.thoughtcrime.securesms.database;
interface ThreadIdDatabaseReference {
void remapThread(long fromId, long toId);
}