mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 18:00:02 +01:00
Setup backupV2 infrastructure and testing.
Co-authored-by: Clark Chen <clark@signal.org>
This commit is contained in:
committed by
Cody Henthorne
parent
feb74d90f6
commit
b540b5813e
@@ -79,11 +79,11 @@ public abstract class DatabaseTable {
|
||||
this.databaseHelper = databaseHelper;
|
||||
}
|
||||
|
||||
protected SQLiteDatabase getReadableDatabase() {
|
||||
public SQLiteDatabase getReadableDatabase() {
|
||||
return databaseHelper.getSignalReadableDatabase();
|
||||
}
|
||||
|
||||
protected SQLiteDatabase getWritableDatabase() {
|
||||
public SQLiteDatabase getWritableDatabase() {
|
||||
return databaseHelper.getSignalWritableDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign
|
||||
val LIST_UI_PROJECTION = arrayOf(ID, NAME, RECIPIENT_ID, ALLOWS_REPLIES, IS_UNKNOWN, PRIVACY_MODE, SEARCH_NAME)
|
||||
}
|
||||
|
||||
private object MembershipTable {
|
||||
object MembershipTable {
|
||||
const val TABLE_NAME = "distribution_list_member"
|
||||
|
||||
const val ID = "_id"
|
||||
|
||||
@@ -2,9 +2,11 @@ package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import androidx.core.content.contentValuesOf
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.delete
|
||||
import org.signal.core.util.forEach
|
||||
import org.signal.core.util.readToList
|
||||
import org.signal.core.util.requireBoolean
|
||||
import org.signal.core.util.requireInt
|
||||
@@ -21,9 +23,9 @@ class GroupReceiptTable(context: Context?, databaseHelper: SignalDatabase?) : Da
|
||||
private const val ID = "_id"
|
||||
const val MMS_ID = "mms_id"
|
||||
const val RECIPIENT_ID = "address"
|
||||
private const val STATUS = "status"
|
||||
private const val TIMESTAMP = "timestamp"
|
||||
private const val UNIDENTIFIED = "unidentified"
|
||||
const val STATUS = "status"
|
||||
const val TIMESTAMP = "timestamp"
|
||||
const val UNIDENTIFIED = "unidentified"
|
||||
const val STATUS_UNKNOWN = -1
|
||||
const val STATUS_UNDELIVERED = 0
|
||||
const val STATUS_DELIVERED = 1
|
||||
@@ -127,14 +129,32 @@ class GroupReceiptTable(context: Context?, databaseHelper: SignalDatabase?) : Da
|
||||
.from(TABLE_NAME)
|
||||
.where("$MMS_ID = ?", mmsId)
|
||||
.run()
|
||||
.readToList { cursor ->
|
||||
GroupReceiptInfo(
|
||||
recipientId = RecipientId.from(cursor.requireLong(RECIPIENT_ID)),
|
||||
status = cursor.requireInt(STATUS),
|
||||
timestamp = cursor.requireLong(TIMESTAMP),
|
||||
isUnidentified = cursor.requireBoolean(UNIDENTIFIED)
|
||||
)
|
||||
}
|
||||
.readToList { it.toGroupReceiptInfo() }
|
||||
}
|
||||
|
||||
fun getGroupReceiptInfoForMessages(ids: Set<Long>): Map<Long, List<GroupReceiptInfo>> {
|
||||
if (ids.isEmpty()) {
|
||||
return emptyMap()
|
||||
}
|
||||
|
||||
val messageIdsToGroupReceipts: MutableMap<Long, MutableList<GroupReceiptInfo>> = mutableMapOf()
|
||||
|
||||
val args: List<Array<String>> = ids.map { SqlUtil.buildArgs(it) }
|
||||
|
||||
SqlUtil.buildCustomCollectionQuery("$MMS_ID = ?", args).forEach { query ->
|
||||
readableDatabase
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.where(query.where, query.whereArgs)
|
||||
.run()
|
||||
.forEach { cursor ->
|
||||
val messageId = cursor.requireLong(MMS_ID)
|
||||
val receipts = messageIdsToGroupReceipts.getOrPut(messageId) { mutableListOf() }
|
||||
receipts += cursor.toGroupReceiptInfo()
|
||||
}
|
||||
}
|
||||
|
||||
return messageIdsToGroupReceipts
|
||||
}
|
||||
|
||||
fun deleteRowsForMessage(mmsId: Long) {
|
||||
@@ -163,6 +183,15 @@ class GroupReceiptTable(context: Context?, databaseHelper: SignalDatabase?) : Da
|
||||
.run()
|
||||
}
|
||||
|
||||
private fun Cursor.toGroupReceiptInfo(): GroupReceiptInfo {
|
||||
return GroupReceiptInfo(
|
||||
recipientId = RecipientId.from(this.requireLong(RECIPIENT_ID)),
|
||||
status = this.requireInt(STATUS),
|
||||
timestamp = this.requireLong(TIMESTAMP),
|
||||
isUnidentified = this.requireBoolean(UNIDENTIFIED)
|
||||
)
|
||||
}
|
||||
|
||||
data class GroupReceiptInfo(
|
||||
val recipientId: RecipientId,
|
||||
val status: Int,
|
||||
|
||||
@@ -1770,7 +1770,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return threads.getOrCreateThreadIdFor(recipient)
|
||||
}
|
||||
|
||||
private fun rawQueryWithAttachments(where: String, arguments: Array<String>?, reverse: Boolean = false, limit: Long = 0): Cursor {
|
||||
fun rawQueryWithAttachments(where: String, arguments: Array<String>?, reverse: Boolean = false, limit: Long = 0): Cursor {
|
||||
return rawQueryWithAttachments(MMS_PROJECTION_WITH_ATTACHMENTS, where, arguments, reverse, limit)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ class ReactionTable(context: Context, databaseHelper: SignalDatabase) : Database
|
||||
|
||||
private const val ID = "_id"
|
||||
const val MESSAGE_ID = "message_id"
|
||||
private const val AUTHOR_ID = "author_id"
|
||||
private const val EMOJI = "emoji"
|
||||
private const val DATE_SENT = "date_sent"
|
||||
private const val DATE_RECEIVED = "date_received"
|
||||
const val AUTHOR_ID = "author_id"
|
||||
const val EMOJI = "emoji"
|
||||
const val DATE_SENT = "date_sent"
|
||||
const val DATE_RECEIVED = "date_received"
|
||||
|
||||
@JvmField
|
||||
val CREATE_TABLE = """
|
||||
|
||||
@@ -17,10 +17,7 @@ import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.delete
|
||||
import org.signal.core.util.exists
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.optionalBlob
|
||||
import org.signal.core.util.optionalBoolean
|
||||
import org.signal.core.util.optionalInt
|
||||
import org.signal.core.util.optionalLong
|
||||
import org.signal.core.util.nullIfBlank
|
||||
import org.signal.core.util.optionalString
|
||||
import org.signal.core.util.or
|
||||
import org.signal.core.util.orNull
|
||||
@@ -29,7 +26,6 @@ import org.signal.core.util.readToSet
|
||||
import org.signal.core.util.readToSingleBoolean
|
||||
import org.signal.core.util.readToSingleLong
|
||||
import org.signal.core.util.requireBlob
|
||||
import org.signal.core.util.requireBoolean
|
||||
import org.signal.core.util.requireInt
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireNonNullString
|
||||
@@ -39,11 +35,9 @@ import org.signal.core.util.update
|
||||
import org.signal.core.util.withinTransaction
|
||||
import org.signal.libsignal.protocol.IdentityKey
|
||||
import org.signal.libsignal.protocol.InvalidKeyException
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException
|
||||
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.thoughtcrime.securesms.badges.Badges.toDatabaseBadge
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.color.MaterialColor
|
||||
@@ -58,6 +52,7 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||
import org.thoughtcrime.securesms.database.GroupTable.LegacyGroupInsertException
|
||||
import org.thoughtcrime.securesms.database.GroupTable.ShowAsStoryState
|
||||
import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus
|
||||
import org.thoughtcrime.securesms.database.RecipientTableCursorUtil.getRecipientExtras
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groups
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.identities
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.runPostSuccessfulTransaction
|
||||
@@ -84,7 +79,6 @@ import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor
|
||||
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
@@ -93,12 +87,10 @@ import org.thoughtcrime.securesms.storage.StorageRecordUpdate
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncModels
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.GroupUtil
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperFactory
|
||||
import org.thoughtcrime.securesms.wallpaper.WallpaperStorage
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
@@ -113,7 +105,6 @@ import org.whispersystems.signalservice.api.storage.StorageId
|
||||
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record
|
||||
import java.io.Closeable
|
||||
import java.io.IOException
|
||||
import java.util.Arrays
|
||||
import java.util.Collections
|
||||
import java.util.LinkedList
|
||||
import java.util.Objects
|
||||
@@ -123,9 +114,9 @@ import kotlin.math.max
|
||||
|
||||
open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(RecipientTable::class.java)
|
||||
val TAG = Log.tag(RecipientTable::class.java)
|
||||
|
||||
companion object {
|
||||
private val UNREGISTERED_LIFESPAN: Long = TimeUnit.DAYS.toMillis(30)
|
||||
|
||||
const val TABLE_NAME = "recipient"
|
||||
@@ -651,6 +642,15 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
}
|
||||
|
||||
fun getAll(): RecipientIterator {
|
||||
val cursor = readableDatabase
|
||||
.select()
|
||||
.from(TABLE_NAME)
|
||||
.run()
|
||||
|
||||
return RecipientIterator(context, cursor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Only call once to create initial release channel recipient.
|
||||
*/
|
||||
@@ -704,7 +704,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
|
||||
readableDatabase.query(TABLE_NAME, RECIPIENT_PROJECTION, query, args, null, null, null).use { cursor ->
|
||||
return if (cursor != null && cursor.moveToNext()) {
|
||||
getRecord(context, cursor)
|
||||
RecipientTableCursorUtil.getRecord(context, cursor)
|
||||
} else {
|
||||
findRemappedIdRecord(id)
|
||||
}
|
||||
@@ -1113,7 +1113,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
|
||||
readableDatabase.query(table, columns, query, args, "$TABLE_NAME.$ID", null, null).use { cursor ->
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
out.add(getRecord(context, cursor))
|
||||
out.add(RecipientTableCursorUtil.getRecord(context, cursor))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1709,9 +1709,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
|
||||
fun setProfileName(id: RecipientId, profileName: ProfileName) {
|
||||
val contentValues = ContentValues(1).apply {
|
||||
put(PROFILE_GIVEN_NAME, profileName.givenName)
|
||||
put(PROFILE_FAMILY_NAME, profileName.familyName)
|
||||
put(PROFILE_JOINED_NAME, profileName.toString())
|
||||
put(PROFILE_GIVEN_NAME, profileName.givenName.nullIfBlank())
|
||||
put(PROFILE_FAMILY_NAME, profileName.familyName.nullIfBlank())
|
||||
put(PROFILE_JOINED_NAME, profileName.toString().nullIfBlank())
|
||||
}
|
||||
if (update(id, contentValues)) {
|
||||
rotateStorageId(id)
|
||||
@@ -3177,7 +3177,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
.where("$ID LIKE ? OR $ACI_COLUMN LIKE ? OR $PNI_COLUMN LIKE ?", "%$query%", "%$query%", "%$query%")
|
||||
.run()
|
||||
.readToList { cursor ->
|
||||
getRecord(context, cursor)
|
||||
RecipientTableCursorUtil.getRecord(context, cursor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3650,7 +3650,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
.run()
|
||||
.use { cursor ->
|
||||
return if (cursor.moveToFirst()) {
|
||||
readCapabilities(cursor)
|
||||
RecipientTableCursorUtil.readCapabilities(cursor)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -4110,196 +4110,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
RecipientId.clearCache()
|
||||
}
|
||||
|
||||
fun getRecord(context: Context, cursor: Cursor): RecipientRecord {
|
||||
return getRecord(context, cursor, ID)
|
||||
}
|
||||
|
||||
fun getRecord(context: Context, cursor: Cursor, idColumnName: String): RecipientRecord {
|
||||
val profileKeyString = cursor.requireString(PROFILE_KEY)
|
||||
val expiringProfileKeyCredentialString = cursor.requireString(EXPIRING_PROFILE_KEY_CREDENTIAL)
|
||||
var profileKey: ByteArray? = null
|
||||
var expiringProfileKeyCredential: ExpiringProfileKeyCredential? = null
|
||||
|
||||
if (profileKeyString != null) {
|
||||
try {
|
||||
profileKey = Base64.decode(profileKeyString)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
|
||||
if (expiringProfileKeyCredentialString != null) {
|
||||
try {
|
||||
val columnDataBytes = Base64.decode(expiringProfileKeyCredentialString)
|
||||
val columnData = ExpiringProfileKeyCredentialColumnData.ADAPTER.decode(columnDataBytes)
|
||||
if (Arrays.equals(columnData.profileKey.toByteArray(), profileKey)) {
|
||||
expiringProfileKeyCredential = ExpiringProfileKeyCredential(columnData.expiringProfileKeyCredential.toByteArray())
|
||||
} else {
|
||||
Log.i(TAG, "Out of date profile key credential data ignored on read")
|
||||
}
|
||||
} catch (e: InvalidInputException) {
|
||||
Log.w(TAG, "Profile key credential column data could not be read", e)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Profile key credential column data could not be read", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val serializedWallpaper = cursor.requireBlob(WALLPAPER)
|
||||
val chatWallpaper: ChatWallpaper? = if (serializedWallpaper != null) {
|
||||
try {
|
||||
ChatWallpaperFactory.create(Wallpaper.ADAPTER.decode(serializedWallpaper))
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Failed to parse wallpaper.", e)
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val customChatColorsId = cursor.requireLong(CUSTOM_CHAT_COLORS_ID)
|
||||
val serializedChatColors = cursor.requireBlob(CHAT_COLORS)
|
||||
val chatColors: ChatColors? = if (serializedChatColors != null) {
|
||||
try {
|
||||
forChatColor(forLongValue(customChatColorsId), ChatColor.ADAPTER.decode(serializedChatColors))
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Failed to parse chat colors.", e)
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val recipientId = RecipientId.from(cursor.requireLong(idColumnName))
|
||||
val distributionListId: DistributionListId? = DistributionListId.fromNullable(cursor.requireLong(DISTRIBUTION_LIST_ID))
|
||||
val avatarColor: AvatarColor = if (distributionListId != null) AvatarColor.UNKNOWN else AvatarColor.deserialize(cursor.requireString(AVATAR_COLOR))
|
||||
|
||||
return RecipientRecord(
|
||||
id = recipientId,
|
||||
aci = ACI.parseOrNull(cursor.requireString(ACI_COLUMN)),
|
||||
pni = PNI.parsePrefixedOrNull(cursor.requireString(PNI_COLUMN)),
|
||||
username = cursor.requireString(USERNAME),
|
||||
e164 = cursor.requireString(E164),
|
||||
email = cursor.requireString(EMAIL),
|
||||
groupId = GroupId.parseNullableOrThrow(cursor.requireString(GROUP_ID)),
|
||||
distributionListId = distributionListId,
|
||||
recipientType = RecipientType.fromId(cursor.requireInt(TYPE)),
|
||||
isBlocked = cursor.requireBoolean(BLOCKED),
|
||||
muteUntil = cursor.requireLong(MUTE_UNTIL),
|
||||
messageVibrateState = VibrateState.fromId(cursor.requireInt(MESSAGE_VIBRATE)),
|
||||
callVibrateState = VibrateState.fromId(cursor.requireInt(CALL_VIBRATE)),
|
||||
messageRingtone = Util.uri(cursor.requireString(MESSAGE_RINGTONE)),
|
||||
callRingtone = Util.uri(cursor.requireString(CALL_RINGTONE)),
|
||||
expireMessages = cursor.requireInt(MESSAGE_EXPIRATION_TIME),
|
||||
registered = RegisteredState.fromId(cursor.requireInt(REGISTERED)),
|
||||
profileKey = profileKey,
|
||||
expiringProfileKeyCredential = expiringProfileKeyCredential,
|
||||
systemProfileName = ProfileName.fromParts(cursor.requireString(SYSTEM_GIVEN_NAME), cursor.requireString(SYSTEM_FAMILY_NAME)),
|
||||
systemDisplayName = cursor.requireString(SYSTEM_JOINED_NAME),
|
||||
systemContactPhotoUri = cursor.requireString(SYSTEM_PHOTO_URI),
|
||||
systemPhoneLabel = cursor.requireString(SYSTEM_PHONE_LABEL),
|
||||
systemContactUri = cursor.requireString(SYSTEM_CONTACT_URI),
|
||||
signalProfileName = ProfileName.fromParts(cursor.requireString(PROFILE_GIVEN_NAME), cursor.requireString(PROFILE_FAMILY_NAME)),
|
||||
signalProfileAvatar = cursor.requireString(PROFILE_AVATAR),
|
||||
profileAvatarFileDetails = AvatarHelper.getAvatarFileDetails(context, recipientId),
|
||||
profileSharing = cursor.requireBoolean(PROFILE_SHARING),
|
||||
lastProfileFetch = cursor.requireLong(LAST_PROFILE_FETCH),
|
||||
notificationChannel = cursor.requireString(NOTIFICATION_CHANNEL),
|
||||
unidentifiedAccessMode = UnidentifiedAccessMode.fromMode(cursor.requireInt(SEALED_SENDER_MODE)),
|
||||
capabilities = readCapabilities(cursor),
|
||||
storageId = Base64.decodeNullableOrThrow(cursor.requireString(STORAGE_SERVICE_ID)),
|
||||
mentionSetting = MentionSetting.fromId(cursor.requireInt(MENTION_SETTING)),
|
||||
wallpaper = chatWallpaper,
|
||||
chatColors = chatColors,
|
||||
avatarColor = avatarColor,
|
||||
about = cursor.requireString(ABOUT),
|
||||
aboutEmoji = cursor.requireString(ABOUT_EMOJI),
|
||||
syncExtras = getSyncExtras(cursor),
|
||||
extras = getExtras(cursor),
|
||||
hasGroupsInCommon = cursor.requireBoolean(GROUPS_IN_COMMON),
|
||||
badges = parseBadgeList(cursor.requireBlob(BADGES)),
|
||||
needsPniSignature = cursor.requireBoolean(NEEDS_PNI_SIGNATURE),
|
||||
hiddenState = Recipient.HiddenState.deserialize(cursor.requireInt(HIDDEN)),
|
||||
callLinkRoomId = cursor.requireString(CALL_LINK_ROOM_ID)?.let { CallLinkRoomId.DatabaseSerializer.deserialize(it) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun readCapabilities(cursor: Cursor): RecipientRecord.Capabilities {
|
||||
val capabilities = cursor.requireLong(CAPABILITIES)
|
||||
return RecipientRecord.Capabilities(
|
||||
rawBits = capabilities,
|
||||
groupsV1MigrationCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.GROUPS_V1_MIGRATION, Capabilities.BIT_LENGTH).toInt()),
|
||||
senderKeyCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.SENDER_KEY, Capabilities.BIT_LENGTH).toInt()),
|
||||
announcementGroupCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.ANNOUNCEMENT_GROUPS, Capabilities.BIT_LENGTH).toInt()),
|
||||
changeNumberCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.CHANGE_NUMBER, Capabilities.BIT_LENGTH).toInt()),
|
||||
storiesCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.STORIES, Capabilities.BIT_LENGTH).toInt()),
|
||||
giftBadgesCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.GIFT_BADGES, Capabilities.BIT_LENGTH).toInt()),
|
||||
pnpCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.PNP, Capabilities.BIT_LENGTH).toInt()),
|
||||
paymentActivation = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.PAYMENT_ACTIVATION, Capabilities.BIT_LENGTH).toInt())
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseBadgeList(serializedBadgeList: ByteArray?): List<Badge> {
|
||||
var badgeList: BadgeList? = null
|
||||
if (serializedBadgeList != null) {
|
||||
try {
|
||||
badgeList = BadgeList.ADAPTER.decode(serializedBadgeList)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
}
|
||||
|
||||
val badges: List<Badge>
|
||||
if (badgeList != null) {
|
||||
val protoBadges = badgeList.badges
|
||||
badges = ArrayList(protoBadges.size)
|
||||
for (protoBadge in protoBadges) {
|
||||
badges.add(Badges.fromDatabaseBadge(protoBadge))
|
||||
}
|
||||
} else {
|
||||
badges = emptyList()
|
||||
}
|
||||
|
||||
return badges
|
||||
}
|
||||
|
||||
private fun getSyncExtras(cursor: Cursor): RecipientRecord.SyncExtras {
|
||||
val storageProtoRaw = cursor.optionalString(STORAGE_SERVICE_PROTO).orElse(null)
|
||||
val storageProto = if (storageProtoRaw != null) Base64.decodeOrThrow(storageProtoRaw) else null
|
||||
val archived = cursor.optionalBoolean(ThreadTable.ARCHIVED).orElse(false)
|
||||
val forcedUnread = cursor.optionalInt(ThreadTable.READ).map { status: Int -> status == ThreadTable.ReadStatus.FORCED_UNREAD.serialize() }.orElse(false)
|
||||
val groupMasterKey = cursor.optionalBlob(GroupTable.V2_MASTER_KEY).map { GroupUtil.requireMasterKey(it) }.orElse(null)
|
||||
val identityKey = cursor.optionalString(IDENTITY_KEY).map { Base64.decodeOrThrow(it) }.orElse(null)
|
||||
val identityStatus = cursor.optionalInt(IDENTITY_STATUS).map { VerifiedStatus.forState(it) }.orElse(VerifiedStatus.DEFAULT)
|
||||
val unregisteredTimestamp = cursor.optionalLong(UNREGISTERED_TIMESTAMP).orElse(0)
|
||||
val systemNickname = cursor.optionalString(SYSTEM_NICKNAME).orElse(null)
|
||||
|
||||
return RecipientRecord.SyncExtras(
|
||||
storageProto = storageProto,
|
||||
groupMasterKey = groupMasterKey,
|
||||
identityKey = identityKey,
|
||||
identityStatus = identityStatus,
|
||||
isArchived = archived,
|
||||
isForcedUnread = forcedUnread,
|
||||
unregisteredTimestamp = unregisteredTimestamp,
|
||||
systemNickname = systemNickname
|
||||
)
|
||||
}
|
||||
|
||||
private fun getExtras(cursor: Cursor): Recipient.Extras? {
|
||||
return Recipient.Extras.from(getRecipientExtras(cursor))
|
||||
}
|
||||
|
||||
private fun getRecipientExtras(cursor: Cursor): RecipientExtras? {
|
||||
return cursor.optionalBlob(EXTRAS).map { b: ByteArray ->
|
||||
try {
|
||||
RecipientExtras.ADAPTER.decode(b)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, e)
|
||||
throw AssertionError(e)
|
||||
}
|
||||
}.orElse(null)
|
||||
}
|
||||
|
||||
private fun updateProfileValuesForMerge(values: ContentValues, record: RecipientRecord) {
|
||||
values.apply {
|
||||
put(PROFILE_KEY, if (record.profileKey != null) Base64.encodeWithPadding(record.profileKey) else null)
|
||||
@@ -4430,6 +4240,28 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
}
|
||||
|
||||
class RecipientIterator(
|
||||
private val context: Context,
|
||||
private val cursor: Cursor
|
||||
) : Iterator<RecipientRecord>, Closeable {
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return cursor.count != 0 && !cursor.isLast
|
||||
}
|
||||
|
||||
override fun next(): RecipientRecord {
|
||||
if (!cursor.moveToNext()) {
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
|
||||
return RecipientTableCursorUtil.getRecord(context, cursor)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
class MissingRecipientException(id: RecipientId?) : IllegalStateException("Failed to find recipient with ID: $id")
|
||||
|
||||
private class GetOrInsertResult(val recipientId: RecipientId, val neededInsert: Boolean)
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Bitmask
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.optionalBlob
|
||||
import org.signal.core.util.optionalBoolean
|
||||
import org.signal.core.util.optionalInt
|
||||
import org.signal.core.util.optionalLong
|
||||
import org.signal.core.util.optionalString
|
||||
import org.signal.core.util.requireBlob
|
||||
import org.signal.core.util.requireBoolean
|
||||
import org.signal.core.util.requireInt
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireString
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException
|
||||
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.Capabilities
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
import org.thoughtcrime.securesms.database.model.RecipientRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BadgeList
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ChatColor
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ExpiringProfileKeyCredentialColumnData
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import org.thoughtcrime.securesms.util.GroupUtil
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperFactory
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import java.io.IOException
|
||||
import java.util.Arrays
|
||||
|
||||
object RecipientTableCursorUtil {
|
||||
|
||||
private val TAG = Log.tag(RecipientTableCursorUtil::class.java)
|
||||
|
||||
fun getRecord(context: Context, cursor: Cursor): RecipientRecord {
|
||||
return getRecord(context, cursor, RecipientTable.ID)
|
||||
}
|
||||
|
||||
fun getRecord(context: Context, cursor: Cursor, idColumnName: String): RecipientRecord {
|
||||
val profileKeyString = cursor.requireString(RecipientTable.PROFILE_KEY)
|
||||
val expiringProfileKeyCredentialString = cursor.requireString(RecipientTable.EXPIRING_PROFILE_KEY_CREDENTIAL)
|
||||
var profileKey: ByteArray? = null
|
||||
var expiringProfileKeyCredential: ExpiringProfileKeyCredential? = null
|
||||
|
||||
if (profileKeyString != null) {
|
||||
try {
|
||||
profileKey = Base64.decode(profileKeyString)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
|
||||
if (expiringProfileKeyCredentialString != null) {
|
||||
try {
|
||||
val columnDataBytes = Base64.decode(expiringProfileKeyCredentialString)
|
||||
val columnData = ExpiringProfileKeyCredentialColumnData.ADAPTER.decode(columnDataBytes)
|
||||
if (Arrays.equals(columnData.profileKey.toByteArray(), profileKey)) {
|
||||
expiringProfileKeyCredential = ExpiringProfileKeyCredential(columnData.expiringProfileKeyCredential.toByteArray())
|
||||
} else {
|
||||
Log.i(TAG, "Out of date profile key credential data ignored on read")
|
||||
}
|
||||
} catch (e: InvalidInputException) {
|
||||
Log.w(TAG, "Profile key credential column data could not be read", e)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Profile key credential column data could not be read", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val serializedWallpaper = cursor.requireBlob(RecipientTable.WALLPAPER)
|
||||
val chatWallpaper: ChatWallpaper? = if (serializedWallpaper != null) {
|
||||
try {
|
||||
ChatWallpaperFactory.create(Wallpaper.ADAPTER.decode(serializedWallpaper))
|
||||
} catch (e: InvalidProtocolBufferException) {
|
||||
Log.w(TAG, "Failed to parse wallpaper.", e)
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val customChatColorsId = cursor.requireLong(RecipientTable.CUSTOM_CHAT_COLORS_ID)
|
||||
val serializedChatColors = cursor.requireBlob(RecipientTable.CHAT_COLORS)
|
||||
val chatColors: ChatColors? = if (serializedChatColors != null) {
|
||||
try {
|
||||
ChatColors.forChatColor(ChatColors.Id.forLongValue(customChatColorsId), ChatColor.ADAPTER.decode(serializedChatColors))
|
||||
} catch (e: InvalidProtocolBufferException) {
|
||||
Log.w(TAG, "Failed to parse chat colors.", e)
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val recipientId = RecipientId.from(cursor.requireLong(idColumnName))
|
||||
val distributionListId: DistributionListId? = DistributionListId.fromNullable(cursor.requireLong(RecipientTable.DISTRIBUTION_LIST_ID))
|
||||
val avatarColor: AvatarColor = if (distributionListId != null) AvatarColor.UNKNOWN else AvatarColor.deserialize(cursor.requireString(RecipientTable.AVATAR_COLOR))
|
||||
|
||||
return RecipientRecord(
|
||||
id = recipientId,
|
||||
aci = ServiceId.ACI.parseOrNull(cursor.requireString(RecipientTable.ACI_COLUMN)),
|
||||
pni = ServiceId.PNI.parsePrefixedOrNull(cursor.requireString(RecipientTable.PNI_COLUMN)),
|
||||
username = cursor.requireString(RecipientTable.USERNAME),
|
||||
e164 = cursor.requireString(RecipientTable.E164),
|
||||
email = cursor.requireString(RecipientTable.EMAIL),
|
||||
groupId = GroupId.parseNullableOrThrow(cursor.requireString(RecipientTable.GROUP_ID)),
|
||||
distributionListId = distributionListId,
|
||||
recipientType = RecipientTable.RecipientType.fromId(cursor.requireInt(RecipientTable.TYPE)),
|
||||
isBlocked = cursor.requireBoolean(RecipientTable.BLOCKED),
|
||||
muteUntil = cursor.requireLong(RecipientTable.MUTE_UNTIL),
|
||||
messageVibrateState = RecipientTable.VibrateState.fromId(cursor.requireInt(RecipientTable.MESSAGE_VIBRATE)),
|
||||
callVibrateState = RecipientTable.VibrateState.fromId(cursor.requireInt(RecipientTable.CALL_VIBRATE)),
|
||||
messageRingtone = Util.uri(cursor.requireString(RecipientTable.MESSAGE_RINGTONE)),
|
||||
callRingtone = Util.uri(cursor.requireString(RecipientTable.CALL_RINGTONE)),
|
||||
expireMessages = cursor.requireInt(RecipientTable.MESSAGE_EXPIRATION_TIME),
|
||||
registered = RegisteredState.fromId(cursor.requireInt(RecipientTable.REGISTERED)),
|
||||
profileKey = profileKey,
|
||||
expiringProfileKeyCredential = expiringProfileKeyCredential,
|
||||
systemProfileName = ProfileName.fromParts(cursor.requireString(RecipientTable.SYSTEM_GIVEN_NAME), cursor.requireString(RecipientTable.SYSTEM_FAMILY_NAME)),
|
||||
systemDisplayName = cursor.requireString(RecipientTable.SYSTEM_JOINED_NAME),
|
||||
systemContactPhotoUri = cursor.requireString(RecipientTable.SYSTEM_PHOTO_URI),
|
||||
systemPhoneLabel = cursor.requireString(RecipientTable.SYSTEM_PHONE_LABEL),
|
||||
systemContactUri = cursor.requireString(RecipientTable.SYSTEM_CONTACT_URI),
|
||||
signalProfileName = ProfileName.fromParts(cursor.requireString(RecipientTable.PROFILE_GIVEN_NAME), cursor.requireString(RecipientTable.PROFILE_FAMILY_NAME)),
|
||||
signalProfileAvatar = cursor.requireString(RecipientTable.PROFILE_AVATAR),
|
||||
profileAvatarFileDetails = AvatarHelper.getAvatarFileDetails(context, recipientId),
|
||||
profileSharing = cursor.requireBoolean(RecipientTable.PROFILE_SHARING),
|
||||
lastProfileFetch = cursor.requireLong(RecipientTable.LAST_PROFILE_FETCH),
|
||||
notificationChannel = cursor.requireString(RecipientTable.NOTIFICATION_CHANNEL),
|
||||
unidentifiedAccessMode = RecipientTable.UnidentifiedAccessMode.fromMode(cursor.requireInt(RecipientTable.SEALED_SENDER_MODE)),
|
||||
capabilities = readCapabilities(cursor),
|
||||
storageId = Base64.decodeNullableOrThrow(cursor.requireString(RecipientTable.STORAGE_SERVICE_ID)),
|
||||
mentionSetting = RecipientTable.MentionSetting.fromId(cursor.requireInt(RecipientTable.MENTION_SETTING)),
|
||||
wallpaper = chatWallpaper,
|
||||
chatColors = chatColors,
|
||||
avatarColor = avatarColor,
|
||||
about = cursor.requireString(RecipientTable.ABOUT),
|
||||
aboutEmoji = cursor.requireString(RecipientTable.ABOUT_EMOJI),
|
||||
syncExtras = getSyncExtras(cursor),
|
||||
extras = getExtras(cursor),
|
||||
hasGroupsInCommon = cursor.requireBoolean(RecipientTable.GROUPS_IN_COMMON),
|
||||
badges = parseBadgeList(cursor.requireBlob(RecipientTable.BADGES)),
|
||||
needsPniSignature = cursor.requireBoolean(RecipientTable.NEEDS_PNI_SIGNATURE),
|
||||
hiddenState = Recipient.HiddenState.deserialize(cursor.requireInt(RecipientTable.HIDDEN)),
|
||||
callLinkRoomId = cursor.requireString(RecipientTable.CALL_LINK_ROOM_ID)?.let { CallLinkRoomId.DatabaseSerializer.deserialize(it) }
|
||||
)
|
||||
}
|
||||
|
||||
fun readCapabilities(cursor: Cursor): RecipientRecord.Capabilities {
|
||||
val capabilities = cursor.requireLong(RecipientTable.CAPABILITIES)
|
||||
return RecipientRecord.Capabilities(
|
||||
rawBits = capabilities,
|
||||
groupsV1MigrationCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.GROUPS_V1_MIGRATION, Capabilities.BIT_LENGTH).toInt()),
|
||||
senderKeyCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.SENDER_KEY, Capabilities.BIT_LENGTH).toInt()),
|
||||
announcementGroupCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.ANNOUNCEMENT_GROUPS, Capabilities.BIT_LENGTH).toInt()),
|
||||
changeNumberCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.CHANGE_NUMBER, Capabilities.BIT_LENGTH).toInt()),
|
||||
storiesCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.STORIES, Capabilities.BIT_LENGTH).toInt()),
|
||||
giftBadgesCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.GIFT_BADGES, Capabilities.BIT_LENGTH).toInt()),
|
||||
pnpCapability = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.PNP, Capabilities.BIT_LENGTH).toInt()),
|
||||
paymentActivation = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.PAYMENT_ACTIVATION, Capabilities.BIT_LENGTH).toInt())
|
||||
)
|
||||
}
|
||||
|
||||
fun parseBadgeList(serializedBadgeList: ByteArray?): List<Badge> {
|
||||
var badgeList: BadgeList? = null
|
||||
if (serializedBadgeList != null) {
|
||||
try {
|
||||
badgeList = BadgeList.ADAPTER.decode(serializedBadgeList)
|
||||
} catch (e: InvalidProtocolBufferException) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
}
|
||||
|
||||
val badges: List<Badge>
|
||||
if (badgeList != null) {
|
||||
val protoBadges = badgeList.badges
|
||||
badges = ArrayList(protoBadges.size)
|
||||
for (protoBadge in protoBadges) {
|
||||
badges.add(Badges.fromDatabaseBadge(protoBadge))
|
||||
}
|
||||
} else {
|
||||
badges = emptyList()
|
||||
}
|
||||
|
||||
return badges
|
||||
}
|
||||
|
||||
fun getSyncExtras(cursor: Cursor): RecipientRecord.SyncExtras {
|
||||
val storageProtoRaw = cursor.optionalString(RecipientTable.STORAGE_SERVICE_PROTO).orElse(null)
|
||||
val storageProto = if (storageProtoRaw != null) Base64.decodeOrThrow(storageProtoRaw) else null
|
||||
val archived = cursor.optionalBoolean(ThreadTable.ARCHIVED).orElse(false)
|
||||
val forcedUnread = cursor.optionalInt(ThreadTable.READ).map { status: Int -> status == ThreadTable.ReadStatus.FORCED_UNREAD.serialize() }.orElse(false)
|
||||
val groupMasterKey = cursor.optionalBlob(GroupTable.V2_MASTER_KEY).map { GroupUtil.requireMasterKey(it) }.orElse(null)
|
||||
val identityKey = cursor.optionalString(RecipientTable.IDENTITY_KEY).map { Base64.decodeOrThrow(it) }.orElse(null)
|
||||
val identityStatus = cursor.optionalInt(RecipientTable.IDENTITY_STATUS).map { VerifiedStatus.forState(it) }.orElse(VerifiedStatus.DEFAULT)
|
||||
val unregisteredTimestamp = cursor.optionalLong(RecipientTable.UNREGISTERED_TIMESTAMP).orElse(0)
|
||||
val systemNickname = cursor.optionalString(RecipientTable.SYSTEM_NICKNAME).orElse(null)
|
||||
|
||||
return RecipientRecord.SyncExtras(
|
||||
storageProto = storageProto,
|
||||
groupMasterKey = groupMasterKey,
|
||||
identityKey = identityKey,
|
||||
identityStatus = identityStatus,
|
||||
isArchived = archived,
|
||||
isForcedUnread = forcedUnread,
|
||||
unregisteredTimestamp = unregisteredTimestamp,
|
||||
systemNickname = systemNickname
|
||||
)
|
||||
}
|
||||
|
||||
fun getExtras(cursor: Cursor): Recipient.Extras? {
|
||||
return Recipient.Extras.from(getRecipientExtras(cursor))
|
||||
}
|
||||
|
||||
fun getRecipientExtras(cursor: Cursor): RecipientExtras? {
|
||||
return cursor.optionalBlob(RecipientTable.EXTRAS).map { b: ByteArray ->
|
||||
try {
|
||||
RecipientExtras.ADAPTER.decode(b)
|
||||
} catch (e: InvalidProtocolBufferException) {
|
||||
Log.w(TAG, e)
|
||||
throw AssertionError(e)
|
||||
}
|
||||
}.orElse(null)
|
||||
}
|
||||
}
|
||||
@@ -1807,6 +1807,10 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
)
|
||||
}
|
||||
|
||||
fun clearCache() {
|
||||
threadIdCache.clear()
|
||||
}
|
||||
|
||||
private fun createQuery(where: String, orderBy: String, offset: Long, limit: Long): String {
|
||||
val projection = COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION.joinToString(separator = ",")
|
||||
|
||||
@@ -1879,7 +1883,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
|
||||
open fun getCurrent(): ThreadRecord? {
|
||||
val recipientId = RecipientId.from(cursor.requireLong(RECIPIENT_ID))
|
||||
val recipientSettings = recipients.getRecord(context, cursor, RECIPIENT_ID)
|
||||
val recipientSettings = RecipientTableCursorUtil.getRecord(context, cursor, RECIPIENT_ID)
|
||||
|
||||
val recipient: Recipient = if (recipientSettings.groupId != null) {
|
||||
GroupTable.Reader(cursor).getCurrent()?.let { group ->
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.signal.core.util.Base64;
|
||||
@@ -51,11 +52,11 @@ public class IdentityKeyMismatch {
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public RecipientId getRecipientId(@NonNull Context context) {
|
||||
public RecipientId getRecipientId() {
|
||||
if (!TextUtils.isEmpty(recipientId)) {
|
||||
return RecipientId.from(recipientId);
|
||||
} else {
|
||||
return Recipient.external(context, address).getId();
|
||||
return Recipient.external(ApplicationDependencies.getApplication(), address).getId();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import androidx.annotation.NonNull;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
@@ -30,11 +32,11 @@ public class NetworkFailure {
|
||||
public NetworkFailure() {}
|
||||
|
||||
@JsonIgnore
|
||||
public RecipientId getRecipientId(@NonNull Context context) {
|
||||
public RecipientId getRecipientId() {
|
||||
if (!TextUtils.isEmpty(recipientId)) {
|
||||
return RecipientId.from(recipientId);
|
||||
} else {
|
||||
return Recipient.external(context, address).getId();
|
||||
return Recipient.external(ApplicationDependencies.getApplication(), address).getId();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user