diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/IdentityTable.java b/app/src/main/java/org/thoughtcrime/securesms/database/IdentityTable.java
deleted file mode 100644
index 9f1c581b5..000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/database/IdentityTable.java
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * Copyright (C) 2011 Whisper Systems
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package org.thoughtcrime.securesms.database;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.greenrobot.eventbus.EventBus;
-import org.signal.core.util.logging.Log;
-import org.signal.libsignal.protocol.IdentityKey;
-import org.signal.libsignal.protocol.InvalidKeyException;
-import org.thoughtcrime.securesms.database.model.IdentityRecord;
-import org.thoughtcrime.securesms.database.model.IdentityStoreRecord;
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.recipients.RecipientId;
-import org.thoughtcrime.securesms.storage.StorageSyncHelper;
-import org.thoughtcrime.securesms.util.Base64;
-import org.signal.core.util.CursorUtil;
-import org.thoughtcrime.securesms.util.IdentityUtil;
-import org.signal.core.util.SqlUtil;
-import org.whispersystems.signalservice.api.push.ServiceId;
-import org.whispersystems.signalservice.api.util.UuidUtil;
-
-import java.io.IOException;
-import java.util.Optional;
-
-public class IdentityTable extends DatabaseTable {
-
- @SuppressWarnings("unused")
- private static final String TAG = Log.tag(IdentityTable.class);
-
- static final String TABLE_NAME = "identities";
- private static final String ID = "_id";
- static final String ADDRESS = "address";
- static final String IDENTITY_KEY = "identity_key";
- private static final String FIRST_USE = "first_use";
- private static final String TIMESTAMP = "timestamp";
- static final String VERIFIED = "verified";
- private static final String NONBLOCKING_APPROVAL = "nonblocking_approval";
-
- public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
- ADDRESS + " INTEGER UNIQUE, " +
- IDENTITY_KEY + " TEXT, " +
- FIRST_USE + " INTEGER DEFAULT 0, " +
- TIMESTAMP + " INTEGER DEFAULT 0, " +
- VERIFIED + " INTEGER DEFAULT 0, " +
- NONBLOCKING_APPROVAL + " INTEGER DEFAULT 0);";
-
- public enum VerifiedStatus {
- DEFAULT, VERIFIED, UNVERIFIED;
-
- public int toInt() {
- switch (this) {
- case DEFAULT: return 0;
- case VERIFIED: return 1;
- case UNVERIFIED: return 2;
- default: throw new AssertionError();
- }
- }
-
- public static VerifiedStatus forState(int state) {
- switch (state) {
- case 0: return DEFAULT;
- case 1: return VERIFIED;
- case 2: return UNVERIFIED;
- default: throw new AssertionError("No such state: " + state);
- }
- }
- }
-
- IdentityTable(Context context, SignalDatabase databaseHelper) {
- super(context, databaseHelper);
- }
-
- public @Nullable IdentityStoreRecord getIdentityStoreRecord(@NonNull String addressName) {
- SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
- String query = ADDRESS + " = ?";
- String[] args = SqlUtil.buildArgs(addressName);
-
- try (Cursor cursor = database.query(TABLE_NAME, null, query, args, null, null, null)) {
- if (cursor.moveToFirst()) {
- String serializedIdentity = CursorUtil.requireString(cursor, IDENTITY_KEY);
- long timestamp = CursorUtil.requireLong(cursor, TIMESTAMP);
- int verifiedStatus = CursorUtil.requireInt(cursor, VERIFIED);
- boolean nonblockingApproval = CursorUtil.requireBoolean(cursor, NONBLOCKING_APPROVAL);
- boolean firstUse = CursorUtil.requireBoolean(cursor, FIRST_USE);
-
- return new IdentityStoreRecord(addressName,
- new IdentityKey(Base64.decode(serializedIdentity), 0),
- VerifiedStatus.forState(verifiedStatus),
- firstUse,
- timestamp,
- nonblockingApproval);
- } else if (UuidUtil.isUuid(addressName)) {
- Optional byServiceId = SignalDatabase.recipients().getByServiceId(ServiceId.parseOrThrow(addressName));
-
- if (byServiceId.isPresent()) {
- Recipient recipient = Recipient.resolved(byServiceId.get());
-
- if (recipient.hasE164() && !UuidUtil.isUuid(recipient.requireE164())) {
- Log.i(TAG, "Could not find identity for UUID. Attempting E164.");
- return getIdentityStoreRecord(recipient.requireE164());
- } else {
- Log.i(TAG, "Could not find identity for UUID, and our recipient doesn't have an E164.");
- }
- } else {
- Log.i(TAG, "Could not find identity for UUID, and we don't have a recipient.");
- }
- } else {
- Log.i(TAG, "Could not find identity for E164 either.");
- }
- } catch (InvalidKeyException | IOException e) {
- throw new AssertionError(e);
- }
-
- return null;
- }
-
- public void saveIdentity(@NonNull String addressName,
- @NonNull RecipientId recipientId,
- IdentityKey identityKey,
- VerifiedStatus verifiedStatus,
- boolean firstUse,
- long timestamp,
- boolean nonBlockingApproval)
- {
- saveIdentityInternal(addressName, recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval);
- SignalDatabase.recipients().markNeedsSync(recipientId);
- StorageSyncHelper.scheduleSyncForDataChange();
- }
-
- public void setApproval(@NonNull String addressName, @NonNull RecipientId recipientId, boolean nonBlockingApproval) {
- SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
-
- ContentValues contentValues = new ContentValues(2);
- contentValues.put(NONBLOCKING_APPROVAL, nonBlockingApproval);
-
- database.update(TABLE_NAME, contentValues, ADDRESS + " = ?", SqlUtil.buildArgs(addressName));
-
- SignalDatabase.recipients().markNeedsSync(recipientId);
- StorageSyncHelper.scheduleSyncForDataChange();
- }
-
- public void setVerified(@NonNull String addressName, @NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
- SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
-
- String query = ADDRESS + " = ? AND " + IDENTITY_KEY + " = ?";
- String[] args = SqlUtil.buildArgs(addressName, Base64.encodeBytes(identityKey.serialize()));
-
- ContentValues contentValues = new ContentValues(1);
- contentValues.put(VERIFIED, verifiedStatus.toInt());
-
- int updated = database.update(TABLE_NAME, contentValues, query, args);
-
- if (updated > 0) {
- Optional record = getIdentityRecord(addressName);
- if (record.isPresent()) EventBus.getDefault().post(record.get());
- SignalDatabase.recipients().markNeedsSync(recipientId);
- StorageSyncHelper.scheduleSyncForDataChange();
- }
- }
-
- public void updateIdentityAfterSync(@NonNull String addressName, @NonNull RecipientId recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
- Optional existingRecord = getIdentityRecord(addressName);
-
- boolean hadEntry = existingRecord.isPresent();
- boolean keyMatches = hasMatchingKey(addressName, identityKey);
- boolean statusMatches = keyMatches && hasMatchingStatus(addressName, identityKey, verifiedStatus);
-
- if (!keyMatches || !statusMatches) {
- saveIdentityInternal(addressName, recipientId, identityKey, verifiedStatus, !hadEntry, System.currentTimeMillis(), true);
-
- Optional record = getIdentityRecord(addressName);
-
- if (record.isPresent()) {
- EventBus.getDefault().post(record.get());
- }
-
- ApplicationDependencies.getProtocolStore().aci().identities().invalidate(addressName);
- }
-
- if (hadEntry && !keyMatches) {
- Log.w(TAG, "Updated identity key during storage sync for " + addressName + " | Existing: " + existingRecord.get().getIdentityKey().hashCode() + ", New: " + identityKey.hashCode());
- IdentityUtil.markIdentityUpdate(context, recipientId);
- }
- }
-
- public void delete(@NonNull String addressName) {
- databaseHelper.getSignalWritableDatabase().delete(IdentityTable.TABLE_NAME, IdentityTable.ADDRESS + " = ?", SqlUtil.buildArgs(addressName));
- }
-
- private Optional getIdentityRecord(@NonNull String addressName) {
- SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
- String query = ADDRESS + " = ?";
- String[] args = SqlUtil.buildArgs(addressName);
-
- try (Cursor cursor = database.query(TABLE_NAME, null, query, args, null, null, null)) {
- if (cursor.moveToFirst()) {
- return Optional.of(getIdentityRecord(cursor));
- }
- } catch (InvalidKeyException | IOException e) {
- throw new AssertionError(e);
- }
-
- return Optional.empty();
- }
-
- private boolean hasMatchingKey(@NonNull String addressName, IdentityKey identityKey) {
- SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
- String query = ADDRESS + " = ? AND " + IDENTITY_KEY + " = ?";
- String[] args = SqlUtil.buildArgs(addressName, Base64.encodeBytes(identityKey.serialize()));
-
- try (Cursor cursor = db.query(TABLE_NAME, null, query, args, null, null, null)) {
- return cursor != null && cursor.moveToFirst();
- }
- }
-
- private boolean hasMatchingStatus(@NonNull String addressName, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
- SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
- String query = ADDRESS + " = ? AND " + IDENTITY_KEY + " = ? AND " + VERIFIED + " = ?";
- String[] args = SqlUtil.buildArgs(addressName, Base64.encodeBytes(identityKey.serialize()), verifiedStatus.toInt());
-
- try (Cursor cursor = db.query(TABLE_NAME, null, query, args, null, null, null)) {
- return cursor != null && cursor.moveToFirst();
- }
- }
-
- private static @NonNull IdentityRecord getIdentityRecord(@NonNull Cursor cursor) throws IOException, InvalidKeyException {
- String addressName = CursorUtil.requireString(cursor, ADDRESS);
- String serializedIdentity = CursorUtil.requireString(cursor, IDENTITY_KEY);
- long timestamp = CursorUtil.requireLong(cursor, TIMESTAMP);
- int verifiedStatus = CursorUtil.requireInt(cursor, VERIFIED);
- boolean nonblockingApproval = CursorUtil.requireBoolean(cursor, NONBLOCKING_APPROVAL);
- boolean firstUse = CursorUtil.requireBoolean(cursor, FIRST_USE);
- IdentityKey identity = new IdentityKey(Base64.decode(serializedIdentity), 0);
-
- return new IdentityRecord(RecipientId.fromSidOrE164(addressName), identity, VerifiedStatus.forState(verifiedStatus), firstUse, timestamp, nonblockingApproval);
- }
-
- private void saveIdentityInternal(@NonNull String addressName,
- @NonNull RecipientId recipientId,
- IdentityKey identityKey,
- VerifiedStatus verifiedStatus,
- boolean firstUse,
- long timestamp,
- boolean nonBlockingApproval)
- {
- SQLiteDatabase database = databaseHelper.getSignalWritableDatabase();
- String identityKeyString = Base64.encodeBytes(identityKey.serialize());
-
- ContentValues contentValues = new ContentValues();
- contentValues.put(ADDRESS, addressName);
- contentValues.put(IDENTITY_KEY, identityKeyString);
- contentValues.put(TIMESTAMP, timestamp);
- contentValues.put(VERIFIED, verifiedStatus.toInt());
- contentValues.put(NONBLOCKING_APPROVAL, nonBlockingApproval ? 1 : 0);
- contentValues.put(FIRST_USE, firstUse ? 1 : 0);
-
- database.replace(TABLE_NAME, null, contentValues);
-
- EventBus.getDefault().post(new IdentityRecord(recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval));
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/IdentityTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/IdentityTable.kt
new file mode 100644
index 000000000..7b865f1e7
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/IdentityTable.kt
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2011 Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.thoughtcrime.securesms.database
+
+import android.content.Context
+import androidx.core.content.contentValuesOf
+import org.greenrobot.eventbus.EventBus
+import org.signal.core.util.delete
+import org.signal.core.util.exists
+import org.signal.core.util.firstOrNull
+import org.signal.core.util.logging.Log
+import org.signal.core.util.requireBoolean
+import org.signal.core.util.requireInt
+import org.signal.core.util.requireLong
+import org.signal.core.util.requireNonNullString
+import org.signal.core.util.select
+import org.signal.core.util.toOptional
+import org.signal.core.util.update
+import org.signal.libsignal.protocol.IdentityKey
+import org.thoughtcrime.securesms.database.SignalDatabase.Companion.recipients
+import org.thoughtcrime.securesms.database.model.IdentityRecord
+import org.thoughtcrime.securesms.database.model.IdentityStoreRecord
+import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.recipients.Recipient
+import org.thoughtcrime.securesms.recipients.RecipientId
+import org.thoughtcrime.securesms.storage.StorageSyncHelper
+import org.thoughtcrime.securesms.util.Base64
+import org.thoughtcrime.securesms.util.IdentityUtil
+import org.whispersystems.signalservice.api.push.ServiceId
+import org.whispersystems.signalservice.api.util.UuidUtil
+import java.lang.AssertionError
+import java.util.Optional
+
+class IdentityTable internal constructor(context: Context?, databaseHelper: SignalDatabase?) : DatabaseTable(context, databaseHelper) {
+
+ companion object {
+ private val TAG = Log.tag(IdentityTable::class.java)
+ const val TABLE_NAME = "identities"
+ private const val ID = "_id"
+ const val ADDRESS = "address"
+ const val IDENTITY_KEY = "identity_key"
+ private const val FIRST_USE = "first_use"
+ private const val TIMESTAMP = "timestamp"
+ const val VERIFIED = "verified"
+ private const val NONBLOCKING_APPROVAL = "nonblocking_approval"
+ const val CREATE_TABLE = """
+ CREATE TABLE $TABLE_NAME (
+ $ID INTEGER PRIMARY KEY AUTOINCREMENT,
+ $ADDRESS INTEGER UNIQUE,
+ $IDENTITY_KEY TEXT,
+ $FIRST_USE INTEGER DEFAULT 0,
+ $TIMESTAMP INTEGER DEFAULT 0,
+ $VERIFIED INTEGER DEFAULT 0,
+ $NONBLOCKING_APPROVAL INTEGER DEFAULT 0
+ )
+ """
+ }
+
+ fun getIdentityStoreRecord(addressName: String): IdentityStoreRecord? {
+ readableDatabase
+ .select()
+ .from(TABLE_NAME)
+ .where("$ADDRESS = ?", addressName)
+ .run()
+ .use { cursor ->
+ if (cursor.moveToFirst()) {
+ return IdentityStoreRecord(
+ addressName = addressName,
+ identityKey = IdentityKey(Base64.decode(cursor.requireNonNullString(IDENTITY_KEY)), 0),
+ verifiedStatus = VerifiedStatus.forState(cursor.requireInt(VERIFIED)),
+ firstUse = cursor.requireBoolean(FIRST_USE),
+ timestamp = cursor.requireLong(TIMESTAMP),
+ nonblockingApproval = cursor.requireBoolean(NONBLOCKING_APPROVAL)
+ )
+ } else if (UuidUtil.isUuid(addressName)) {
+ val byServiceId = recipients.getByServiceId(ServiceId.parseOrThrow(addressName))
+ if (byServiceId.isPresent) {
+ val recipient = Recipient.resolved(byServiceId.get())
+ if (recipient.hasE164() && !UuidUtil.isUuid(recipient.requireE164())) {
+ Log.i(TAG, "Could not find identity for UUID. Attempting E164.")
+ return getIdentityStoreRecord(recipient.requireE164())
+ } else {
+ Log.i(TAG, "Could not find identity for UUID, and our recipient doesn't have an E164.")
+ }
+ } else {
+ Log.i(TAG, "Could not find identity for UUID, and we don't have a recipient.")
+ }
+ } else {
+ Log.i(TAG, "Could not find identity for E164 either.")
+ }
+ }
+
+ return null
+ }
+
+ fun saveIdentity(
+ addressName: String,
+ recipientId: RecipientId,
+ identityKey: IdentityKey,
+ verifiedStatus: VerifiedStatus,
+ firstUse: Boolean,
+ timestamp: Long,
+ nonBlockingApproval: Boolean
+ ) {
+ saveIdentityInternal(addressName, recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval)
+ recipients.markNeedsSync(recipientId)
+ StorageSyncHelper.scheduleSyncForDataChange()
+ }
+
+ fun setApproval(addressName: String, recipientId: RecipientId, nonBlockingApproval: Boolean) {
+ val updated = writableDatabase
+ .update(TABLE_NAME)
+ .values(NONBLOCKING_APPROVAL to nonBlockingApproval)
+ .where("$ADDRESS = ?", addressName)
+ .run()
+
+ if (updated > 0) {
+ recipients.markNeedsSync(recipientId)
+ StorageSyncHelper.scheduleSyncForDataChange()
+ }
+ }
+
+ fun setVerified(addressName: String, recipientId: RecipientId, identityKey: IdentityKey, verifiedStatus: VerifiedStatus) {
+ val updated = writableDatabase
+ .update(TABLE_NAME)
+ .values(VERIFIED to verifiedStatus.toInt())
+ .where("$ADDRESS = ? AND $IDENTITY_KEY = ?", addressName, Base64.encodeBytes(identityKey.serialize()))
+ .run()
+
+ if (updated > 0) {
+ val record = getIdentityRecord(addressName)
+ if (record.isPresent) {
+ EventBus.getDefault().post(record.get())
+ }
+ recipients.markNeedsSync(recipientId)
+ StorageSyncHelper.scheduleSyncForDataChange()
+ }
+ }
+
+ fun updateIdentityAfterSync(addressName: String, recipientId: RecipientId, identityKey: IdentityKey, verifiedStatus: VerifiedStatus) {
+ val existingRecord = getIdentityRecord(addressName)
+ val hadEntry = existingRecord.isPresent
+ val keyMatches = hasMatchingKey(addressName, identityKey)
+ val statusMatches = keyMatches && hasMatchingStatus(addressName, identityKey, verifiedStatus)
+
+ if (!keyMatches || !statusMatches) {
+ saveIdentityInternal(addressName, recipientId, identityKey, verifiedStatus, !hadEntry, System.currentTimeMillis(), nonBlockingApproval = true)
+
+ val record = getIdentityRecord(addressName)
+ if (record.isPresent) {
+ EventBus.getDefault().post(record.get())
+ }
+
+ ApplicationDependencies.getProtocolStore().aci().identities().invalidate(addressName)
+ }
+
+ if (hadEntry && !keyMatches) {
+ Log.w(TAG, "Updated identity key during storage sync for " + addressName + " | Existing: " + existingRecord.get().identityKey.hashCode() + ", New: " + identityKey.hashCode())
+ IdentityUtil.markIdentityUpdate(context, recipientId)
+ }
+ }
+
+ fun delete(addressName: String) {
+ writableDatabase
+ .delete(TABLE_NAME)
+ .where("$ADDRESS = ?", addressName)
+ .run()
+ }
+
+ private fun getIdentityRecord(addressName: String): Optional {
+ return readableDatabase
+ .select()
+ .from(TABLE_NAME)
+ .where("$ADDRESS = ?", addressName)
+ .run()
+ .firstOrNull { cursor ->
+ IdentityRecord(
+ recipientId = RecipientId.fromSidOrE164(cursor.requireNonNullString(ADDRESS)),
+ identityKey = IdentityKey(Base64.decode(cursor.requireNonNullString(IDENTITY_KEY)), 0),
+ verifiedStatus = VerifiedStatus.forState(cursor.requireInt(VERIFIED)),
+ firstUse = cursor.requireBoolean(FIRST_USE),
+ timestamp = cursor.requireLong(TIMESTAMP),
+ nonblockingApproval = cursor.requireBoolean(NONBLOCKING_APPROVAL)
+ )
+ }
+ .toOptional()
+ }
+
+ private fun hasMatchingKey(addressName: String, identityKey: IdentityKey): Boolean {
+ return readableDatabase
+ .exists(TABLE_NAME)
+ .where("$ADDRESS = ? AND $IDENTITY_KEY = ?", addressName, Base64.encodeBytes(identityKey.serialize()))
+ .run()
+ }
+
+ private fun hasMatchingStatus(addressName: String, identityKey: IdentityKey, verifiedStatus: VerifiedStatus): Boolean {
+ return readableDatabase
+ .exists(TABLE_NAME)
+ .where("$ADDRESS = ? AND $IDENTITY_KEY = ? AND $VERIFIED = ?", addressName, Base64.encodeBytes(identityKey.serialize()), verifiedStatus.toInt())
+ .run()
+ }
+
+ private fun saveIdentityInternal(
+ addressName: String,
+ recipientId: RecipientId,
+ identityKey: IdentityKey,
+ verifiedStatus: VerifiedStatus,
+ firstUse: Boolean,
+ timestamp: Long,
+ nonBlockingApproval: Boolean
+ ) {
+ val contentValues = contentValuesOf(
+ ADDRESS to addressName,
+ IDENTITY_KEY to Base64.encodeBytes(identityKey.serialize()),
+ TIMESTAMP to timestamp,
+ VERIFIED to verifiedStatus.toInt(),
+ NONBLOCKING_APPROVAL to if (nonBlockingApproval) 1 else 0,
+ FIRST_USE to if (firstUse) 1 else 0
+ )
+ writableDatabase.replace(TABLE_NAME, null, contentValues)
+ EventBus.getDefault().post(IdentityRecord(recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval))
+ }
+
+ enum class VerifiedStatus {
+ DEFAULT, VERIFIED, UNVERIFIED;
+
+ fun toInt(): Int {
+ return when (this) {
+ DEFAULT -> 0
+ VERIFIED -> 1
+ UNVERIFIED -> 2
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun forState(state: Int): VerifiedStatus {
+ return when (state) {
+ 0 -> DEFAULT
+ 1 -> VERIFIED
+ 2 -> UNVERIFIED
+ else -> throw AssertionError("No such state: $state")
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.java
index 46310a1a0..54ab76b6f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageTable.java
@@ -268,7 +268,9 @@ public abstract class MessageTable extends DatabaseTable implements MmsSmsColumn
}
public boolean hasSmsExportMessage(long threadId) {
- return SQLiteDatabaseExtensionsKt.exists(getReadableDatabase(), getTableName(), THREAD_ID_WHERE + " AND " + getTypeField() + " = ?", threadId, Types.SMS_EXPORT_TYPE);
+ return SQLiteDatabaseExtensionsKt.exists(getReadableDatabase(), getTableName())
+ .where(THREAD_ID_WHERE + " AND " + getTypeField() + " = ?", threadId, Types.SMS_EXPORT_TYPE)
+ .run();
}
final int getSecureMessageCountForInsights() {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/PendingPniSignatureMessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/PendingPniSignatureMessageTable.kt
index 685db6044..7011a2246 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/PendingPniSignatureMessageTable.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/PendingPniSignatureMessageTable.kt
@@ -75,7 +75,7 @@ class PendingPniSignatureMessageTable(context: Context, databaseHelper: SignalDa
return@withinTransaction
}
- val stillPending: Boolean = db.exists(TABLE_NAME, "$RECIPIENT_ID = ? AND $SENT_TIMESTAMP = ?", recipientId, sentTimestamps)
+ val stillPending: Boolean = db.exists(TABLE_NAME).where("$RECIPIENT_ID = ? AND $SENT_TIMESTAMP = ?", recipientId, sentTimestamps).run()
if (!stillPending) {
Log.i(TAG, "All devices for ($recipientId, $sentTimestamps) have acked the PNI signature message. Clearing flag and removing any other pending receipts.")
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt
index d87614648..8f8e96986 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt
@@ -419,7 +419,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
}
fun isAssociated(serviceId: ServiceId, pni: PNI): Boolean {
- return readableDatabase.exists(TABLE_NAME, "$SERVICE_ID = ? AND $PNI_COLUMN = ?", serviceId.toString(), pni.toString())
+ return readableDatabase.exists(TABLE_NAME).where("$SERVICE_ID = ? AND $PNI_COLUMN = ?", serviceId.toString(), pni.toString()).run()
}
@JvmOverloads
diff --git a/core-util/src/main/java/org/signal/core/util/OptionalExtensions.kt b/core-util/src/main/java/org/signal/core/util/OptionalExtensions.kt
index e09de4346..4f4feadba 100644
--- a/core-util/src/main/java/org/signal/core/util/OptionalExtensions.kt
+++ b/core-util/src/main/java/org/signal/core/util/OptionalExtensions.kt
@@ -12,4 +12,8 @@ fun Optional.or(other: Optional): Optional {
fun Optional.isAbsent(): Boolean {
return !isPresent
+}
+
+fun E?.toOptional(): Optional {
+ return Optional.ofNullable(this)
}
\ No newline at end of file
diff --git a/core-util/src/main/java/org/signal/core/util/SQLiteDatabaseExtensions.kt b/core-util/src/main/java/org/signal/core/util/SQLiteDatabaseExtensions.kt
index c7140e1b7..fdcd16174 100644
--- a/core-util/src/main/java/org/signal/core/util/SQLiteDatabaseExtensions.kt
+++ b/core-util/src/main/java/org/signal/core/util/SQLiteDatabaseExtensions.kt
@@ -38,10 +38,8 @@ fun SupportSQLiteDatabase.getTableRowCount(table: String): Int {
/**
* Checks if a row exists that matches the query.
*/
-fun SupportSQLiteDatabase.exists(table: String, query: String, vararg args: Any): Boolean {
- return this.query("SELECT EXISTS(SELECT 1 FROM $table WHERE $query)", SqlUtil.buildArgs(*args)).use { cursor ->
- cursor.moveToFirst() && cursor.getInt(0) == 1
- }
+fun SupportSQLiteDatabase.exists(table: String): ExistsBuilderPart1 {
+ return ExistsBuilderPart1(this, table)
}
/**
@@ -259,3 +257,32 @@ class DeleteBuilderPart2(
return db.delete(tableName, where, whereArgs)
}
}
+
+class ExistsBuilderPart1(
+ private val db: SupportSQLiteDatabase,
+ private val tableName: String
+) {
+
+ fun where(@Language("sql") where: String, vararg whereArgs: Any): ExistsBuilderPart2 {
+ return ExistsBuilderPart2(db, tableName, where, SqlUtil.buildArgs(*whereArgs))
+ }
+
+ fun run(): Boolean {
+ return db.query("SELECT EXISTS(SELECT 1 FROM $tableName)", null).use { cursor ->
+ cursor.moveToFirst() && cursor.getInt(0) == 1
+ }
+ }
+}
+
+class ExistsBuilderPart2(
+ private val db: SupportSQLiteDatabase,
+ private val tableName: String,
+ private val where: String,
+ private val whereArgs: Array
+) {
+ fun run(): Boolean {
+ return db.query("SELECT EXISTS(SELECT 1 FROM $tableName WHERE $where)", SqlUtil.buildArgs(*whereArgs)).use { cursor ->
+ cursor.moveToFirst() && cursor.getInt(0) == 1
+ }
+ }
+}