package org.thoughtcrime.securesms.database import android.content.Context import android.net.Uri import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.* import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.signal.* import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.OpenGroupV2 import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.utilities.* import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.util.SessionMetaProtocol class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { override fun getUserPublicKey(): String? { return TextSecurePreferences.getLocalNumber(context) } override fun getUserX25519KeyPair(): ECKeyPair { return DatabaseComponent.get(context).lokiAPIDatabase().getUserX25519KeyPair() } override fun getUserDisplayName(): String? { return TextSecurePreferences.getProfileName(context) } override fun getUserProfileKey(): ByteArray? { return ProfileKeyUtil.getProfileKey(context) } override fun getUserProfilePictureURL(): String? { return TextSecurePreferences.getProfilePictureURL(context) } override fun setUserProfilePictureURL(newValue: String) { val ourRecipient = Address.fromSerialized(getUserPublicKey()!!).let { Recipient.from(context, it, false) } TextSecurePreferences.setProfilePictureURL(context, newValue) RetrieveProfileAvatarJob(ourRecipient, newValue) ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, newValue)) } override fun getOrGenerateRegistrationID(): Int { var registrationID = TextSecurePreferences.getLocalRegistrationId(context) if (registrationID == 0) { registrationID = KeyHelper.generateRegistrationId(false) TextSecurePreferences.setLocalRegistrationId(context, registrationID) } return registrationID } override fun persistAttachments(messageID: Long, attachments: List): List { val database = DatabaseComponent.get(context).attachmentDatabase() val databaseAttachments = attachments.mapNotNull { it.toSignalAttachment() } return database.insertAttachments(messageID, databaseAttachments) } override fun getAttachmentsForMessage(messageID: Long): List { val database = DatabaseComponent.get(context).attachmentDatabase() return database.getAttachmentsForMessage(messageID) } override fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? { var messageID: Long? = null val senderAddress = Address.fromSerialized(message.sender!!) val isUserSender = (message.sender!! == getUserPublicKey()) val group: Optional = when { openGroupID != null -> Optional.of(SignalServiceGroup(openGroupID.toByteArray(), SignalServiceGroup.GroupType.PUBLIC_CHAT)) groupPublicKey != null -> { val doubleEncoded = GroupUtil.doubleEncodeGroupID(groupPublicKey) Optional.of(SignalServiceGroup(GroupUtil.getDecodedGroupIDAsData(doubleEncoded), SignalServiceGroup.GroupType.SIGNAL)) } else -> Optional.absent() } val pointers = attachments.mapNotNull { it.toSignalAttachment() } val targetAddress = if (isUserSender && !message.syncTarget.isNullOrEmpty()) { Address.fromSerialized(message.syncTarget!!) } else if (group.isPresent) { Address.fromSerialized(GroupUtil.getEncodedId(group.get())) } else { senderAddress } val targetRecipient = Recipient.from(context, targetAddress, false) if (message.isMediaMessage() || attachments.isNotEmpty()) { val quote: Optional = if (quotes != null) Optional.of(quotes) else Optional.absent() val linkPreviews: Optional> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! }) val mmsDatabase = DatabaseComponent.get(context).mmsDatabase() val insertResult = if (message.sender == getUserPublicKey()) { val mediaMessage = OutgoingMediaMessage.from(message, targetRecipient, pointers, quote.orNull(), linkPreviews.orNull()?.firstOrNull()) mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) } else { // It seems like we have replaced SignalServiceAttachment with SessionServiceAttachment val signalServiceAttachments = attachments.mapNotNull { it.toSignalPointer() } val mediaMessage = IncomingMediaMessage.from(message, senderAddress, targetRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews) mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.receivedTimestamp ?: 0) } if (insertResult.isPresent) { messageID = insertResult.get().messageId } } else { val smsDatabase = DatabaseComponent.get(context).smsDatabase() val isOpenGroupInvitation = (message.openGroupInvitation != null) val insertResult = if (message.sender == getUserPublicKey()) { val textMessage = if (isOpenGroupInvitation) OutgoingTextMessage.fromOpenGroupInvitation(message.openGroupInvitation, targetRecipient, message.sentTimestamp) else OutgoingTextMessage.from(message, targetRecipient) smsDatabase.insertMessageOutbox(message.threadID ?: -1, textMessage, message.sentTimestamp!!) } else { val textMessage = if (isOpenGroupInvitation) IncomingTextMessage.fromOpenGroupInvitation(message.openGroupInvitation, senderAddress, message.sentTimestamp) else IncomingTextMessage.from(message, senderAddress, group, targetRecipient.expireMessages * 1000L) val encrypted = IncomingEncryptedMessage(textMessage, textMessage.messageBody) smsDatabase.insertMessageInbox(encrypted, message.receivedTimestamp ?: 0) } insertResult.orNull()?.let { result -> messageID = result.messageId } } val threadID = message.threadID // open group trim thread job is scheduled after processing in OpenGroupPollerV2 if (openGroupID.isNullOrEmpty() && threadID != null && threadID >= 0) { JobQueue.shared.add(TrimThreadJob(threadID)) } message.serverHash?.let { serverHash -> messageID?.let { id -> DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(id, serverHash) } } return messageID } override fun persistJob(job: Job) { DatabaseComponent.get(context).sessionJobDatabase().persistJob(job) } override fun markJobAsSucceeded(jobId: String) { DatabaseComponent.get(context).sessionJobDatabase().markJobAsSucceeded(jobId) } override fun markJobAsFailedPermanently(jobId: String) { DatabaseComponent.get(context).sessionJobDatabase().markJobAsFailedPermanently(jobId) } override fun getAllPendingJobs(type: String): Map { return DatabaseComponent.get(context).sessionJobDatabase().getAllPendingJobs(type) } override fun getAttachmentUploadJob(attachmentID: Long): AttachmentUploadJob? { return DatabaseComponent.get(context).sessionJobDatabase().getAttachmentUploadJob(attachmentID) } override fun getMessageSendJob(messageSendJobID: String): MessageSendJob? { return DatabaseComponent.get(context).sessionJobDatabase().getMessageSendJob(messageSendJobID) } override fun getMessageReceiveJob(messageReceiveJobID: String): MessageReceiveJob? { return DatabaseComponent.get(context).sessionJobDatabase().getMessageReceiveJob(messageReceiveJobID) } override fun resumeMessageSendJobIfNeeded(messageSendJobID: String) { val job = DatabaseComponent.get(context).sessionJobDatabase().getMessageSendJob(messageSendJobID) ?: return JobQueue.shared.resumePendingSendMessage(job) } override fun isJobCanceled(job: Job): Boolean { return DatabaseComponent.get(context).sessionJobDatabase().isJobCanceled(job) } override fun getAuthToken(room: String, server: String): String? { val id = "$server.$room" return DatabaseComponent.get(context).lokiAPIDatabase().getAuthToken(id) } override fun setAuthToken(room: String, server: String, newValue: String) { val id = "$server.$room" DatabaseComponent.get(context).lokiAPIDatabase().setAuthToken(id, newValue) } override fun removeAuthToken(room: String, server: String) { val id = "$server.$room" DatabaseComponent.get(context).lokiAPIDatabase().setAuthToken(id, null) } override fun getV2OpenGroup(threadId: Long): OpenGroupV2? { if (threadId.toInt() < 0) { return null } val database = databaseHelper.readableDatabase return database.get(LokiThreadDatabase.publicChatTable, "${LokiThreadDatabase.threadID} = ?", arrayOf( threadId.toString() )) { cursor -> val publicChatAsJson = cursor.getString(LokiThreadDatabase.publicChat) OpenGroupV2.fromJSON(publicChatAsJson) } } override fun getOpenGroupPublicKey(server: String): String? { return DatabaseComponent.get(context).lokiAPIDatabase().getOpenGroupPublicKey(server) } override fun setOpenGroupPublicKey(server: String, newValue: String) { DatabaseComponent.get(context).lokiAPIDatabase().setOpenGroupPublicKey(server, newValue) } override fun getLastMessageServerID(room: String, server: String): Long? { return DatabaseComponent.get(context).lokiAPIDatabase().getLastMessageServerID(room, server) } override fun setLastMessageServerID(room: String, server: String, newValue: Long) { DatabaseComponent.get(context).lokiAPIDatabase().setLastMessageServerID(room, server, newValue) } override fun removeLastMessageServerID(room: String, server: String) { DatabaseComponent.get(context).lokiAPIDatabase().removeLastMessageServerID(room, server) } override fun getLastDeletionServerID(room: String, server: String): Long? { return DatabaseComponent.get(context).lokiAPIDatabase().getLastDeletionServerID(room, server) } override fun setLastDeletionServerID(room: String, server: String, newValue: Long) { DatabaseComponent.get(context).lokiAPIDatabase().setLastDeletionServerID(room, server, newValue) } override fun removeLastDeletionServerID(room: String, server: String) { DatabaseComponent.get(context).lokiAPIDatabase().removeLastDeletionServerID(room, server) } override fun setUserCount(room: String, server: String, newValue: Int) { DatabaseComponent.get(context).lokiAPIDatabase().setUserCount(room, server, newValue) } override fun setOpenGroupServerMessageID(messageID: Long, serverID: Long, threadID: Long, isSms: Boolean) { DatabaseComponent.get(context).lokiMessageDatabase().setServerID(messageID, serverID, isSms) DatabaseComponent.get(context).lokiMessageDatabase().setOriginalThreadID(messageID, serverID, threadID) } override fun isDuplicateMessage(timestamp: Long): Boolean { return getReceivedMessageTimestamps().contains(timestamp) } override fun updateTitle(groupID: String, newValue: String) { DatabaseComponent.get(context).groupDatabase().updateTitle(groupID, newValue) } override fun updateProfilePicture(groupID: String, newValue: ByteArray) { DatabaseComponent.get(context).groupDatabase().updateProfilePicture(groupID, newValue) } override fun getReceivedMessageTimestamps(): Set { return SessionMetaProtocol.getTimestamps() } override fun addReceivedMessageTimestamp(timestamp: Long) { SessionMetaProtocol.addTimestamp(timestamp) } override fun removeReceivedMessageTimestamps(timestamps: Set) { SessionMetaProtocol.removeTimestamps(timestamps) } override fun getMessageIdInDatabase(timestamp: Long, author: String): Long? { val database = DatabaseComponent.get(context).mmsSmsDatabase() val address = Address.fromSerialized(author) return database.getMessageFor(timestamp, address)?.getId() } override fun updateSentTimestamp( messageID: Long, isMms: Boolean, openGroupSentTimestamp: Long, threadId: Long ) { if (isMms) { val mmsDb = DatabaseComponent.get(context).mmsDatabase() mmsDb.updateSentTimestamp(messageID, openGroupSentTimestamp, threadId) } else { val smsDb = DatabaseComponent.get(context).smsDatabase() smsDb.updateSentTimestamp(messageID, openGroupSentTimestamp, threadId) } } override fun markAsSent(timestamp: Long, author: String) { val database = DatabaseComponent.get(context).mmsSmsDatabase() val messageRecord = database.getMessageFor(timestamp, author) ?: return if (messageRecord.isMms) { val mmsDatabase = DatabaseComponent.get(context).mmsDatabase() mmsDatabase.markAsSent(messageRecord.getId(), true) } else { val smsDatabase = DatabaseComponent.get(context).smsDatabase() smsDatabase.markAsSent(messageRecord.getId(), true) } } override fun markAsSending(timestamp: Long, author: String) { val database = DatabaseComponent.get(context).mmsSmsDatabase() val messageRecord = database.getMessageFor(timestamp, author) ?: return if (messageRecord.isMms) { val mmsDatabase = DatabaseComponent.get(context).mmsDatabase() mmsDatabase.markAsSending(messageRecord.getId()) } else { val smsDatabase = DatabaseComponent.get(context).smsDatabase() smsDatabase.markAsSending(messageRecord.getId()) messageRecord.isPending } } override fun markUnidentified(timestamp: Long, author: String) { val database = DatabaseComponent.get(context).mmsSmsDatabase() val messageRecord = database.getMessageFor(timestamp, author) ?: return if (messageRecord.isMms) { val mmsDatabase = DatabaseComponent.get(context).mmsDatabase() mmsDatabase.markUnidentified(messageRecord.getId(), true) } else { val smsDatabase = DatabaseComponent.get(context).smsDatabase() smsDatabase.markUnidentified(messageRecord.getId(), true) } } override fun setErrorMessage(timestamp: Long, author: String, error: Exception) { val database = DatabaseComponent.get(context).mmsSmsDatabase() val messageRecord = database.getMessageFor(timestamp, author) ?: return if (messageRecord.isMms) { val mmsDatabase = DatabaseComponent.get(context).mmsDatabase() mmsDatabase.markAsSentFailed(messageRecord.getId()) } else { val smsDatabase = DatabaseComponent.get(context).smsDatabase() smsDatabase.markAsSentFailed(messageRecord.getId()) } if (error.localizedMessage != null) { val message: String if (error is OnionRequestAPI.HTTPRequestFailedAtDestinationException && error.statusCode == 429) { message = "429: Rate limited." } else { message = error.localizedMessage!! } DatabaseComponent.get(context).lokiMessageDatabase().setErrorMessage(messageRecord.getId(), message) } else { DatabaseComponent.get(context).lokiMessageDatabase().setErrorMessage(messageRecord.getId(), error.javaClass.simpleName) } } override fun setMessageServerHash(messageID: Long, serverHash: String) { DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(messageID, serverHash) } override fun getGroup(groupID: String): GroupRecord? { val group = DatabaseComponent.get(context).groupDatabase().getGroup(groupID) return if (group.isPresent) { group.get() } else null } override fun createGroup(groupId: String, title: String?, members: List
, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List
, formationTimestamp: Long) { DatabaseComponent.get(context).groupDatabase().create(groupId, title, members, avatar, relay, admins, formationTimestamp) } override fun isGroupActive(groupPublicKey: String): Boolean { return DatabaseComponent.get(context).groupDatabase().getGroup(GroupUtil.doubleEncodeGroupID(groupPublicKey)).orNull()?.isActive == true } override fun setActive(groupID: String, value: Boolean) { DatabaseComponent.get(context).groupDatabase().setActive(groupID, value) } override fun getZombieMembers(groupID: String): Set { return DatabaseComponent.get(context).groupDatabase().getGroupZombieMembers(groupID).map { it.address.serialize() }.toHashSet() } override fun removeMember(groupID: String, member: Address) { DatabaseComponent.get(context).groupDatabase().removeMember(groupID, member) } override fun updateMembers(groupID: String, members: List
) { DatabaseComponent.get(context).groupDatabase().updateMembers(groupID, members) } override fun setZombieMembers(groupID: String, members: List
) { DatabaseComponent.get(context).groupDatabase().updateZombieMembers(groupID, members) } override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, sentTimestamp: Long) { val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList()) val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true) val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() val infoMessage = IncomingGroupMessage(m, groupID, updateData, true) val smsDB = DatabaseComponent.get(context).smsDatabase() smsDB.insertMessageInbox(infoMessage) } override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, threadID: Long, sentTimestamp: Long) { val userPublicKey = getUserPublicKey() val recipient = Recipient.from(context, Address.fromSerialized(groupID), false) val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: "" val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, true, null, listOf(), listOf()) val mmsDB = DatabaseComponent.get(context).mmsDatabase() val mmsSmsDB = DatabaseComponent.get(context).mmsSmsDatabase() if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) return val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null) mmsDB.markAsSent(infoMessageID, true) } override fun isClosedGroup(publicKey: String): Boolean { val isClosedGroup = DatabaseComponent.get(context).lokiAPIDatabase().isClosedGroup(publicKey) val address = Address.fromSerialized(publicKey) return address.isClosedGroup || isClosedGroup } override fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList { return DatabaseComponent.get(context).lokiAPIDatabase().getClosedGroupEncryptionKeyPairs(groupPublicKey).toMutableList() } override fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair? { return DatabaseComponent.get(context).lokiAPIDatabase().getLatestClosedGroupEncryptionKeyPair(groupPublicKey) } override fun getAllClosedGroupPublicKeys(): Set { return DatabaseComponent.get(context).lokiAPIDatabase().getAllClosedGroupPublicKeys() } override fun getAllActiveClosedGroupPublicKeys(): Set { return DatabaseComponent.get(context).lokiAPIDatabase().getAllClosedGroupPublicKeys().filter { getGroup(GroupUtil.doubleEncodeGroupID(it))?.isActive == true }.toSet() } override fun addClosedGroupPublicKey(groupPublicKey: String) { DatabaseComponent.get(context).lokiAPIDatabase().addClosedGroupPublicKey(groupPublicKey) } override fun removeClosedGroupPublicKey(groupPublicKey: String) { DatabaseComponent.get(context).lokiAPIDatabase().removeClosedGroupPublicKey(groupPublicKey) } override fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String) { DatabaseComponent.get(context).lokiAPIDatabase().addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey) } override fun removeAllClosedGroupEncryptionKeyPairs(groupPublicKey: String) { DatabaseComponent.get(context).lokiAPIDatabase().removeAllClosedGroupEncryptionKeyPairs(groupPublicKey) } override fun updateFormationTimestamp(groupID: String, formationTimestamp: Long) { DatabaseComponent.get(context).groupDatabase() .updateFormationTimestamp(groupID, formationTimestamp) } override fun setExpirationTimer(groupID: String, duration: Int) { val recipient = Recipient.from(context, fromSerialized(groupID), false) DatabaseComponent.get(context).recipientDatabase().setExpireMessages(recipient, duration); } override fun getAllV2OpenGroups(): Map { return DatabaseComponent.get(context).lokiThreadDatabase().getAllV2OpenGroups() } override fun getAllGroups(): List { return DatabaseComponent.get(context).groupDatabase().allGroups } override fun addOpenGroup(urlAsString: String) { OpenGroupManager.addOpenGroup(urlAsString, context) } override fun setProfileSharing(address: Address, value: Boolean) { val recipient = Recipient.from(context, address, false) DatabaseComponent.get(context).recipientDatabase().setProfileSharing(recipient, value) } override fun getOrCreateThreadIdFor(address: Address): Long { val recipient = Recipient.from(context, address, false) return DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) } override fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): Long { val database = DatabaseComponent.get(context).threadDatabase() if (!openGroupID.isNullOrEmpty()) { val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())), false) return database.getThreadIdIfExistsFor(recipient) } else if (!groupPublicKey.isNullOrEmpty()) { val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)), false) return database.getOrCreateThreadIdFor(recipient) } else { val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false) return database.getOrCreateThreadIdFor(recipient) } } override fun getThreadId(publicKeyOrOpenGroupID: String): Long? { val address = Address.fromSerialized(publicKeyOrOpenGroupID) return getThreadId(address) } override fun getThreadId(address: Address): Long? { val recipient = Recipient.from(context, address, false) return getThreadId(recipient) } override fun getThreadId(recipient: Recipient): Long? { val threadID = DatabaseComponent.get(context).threadDatabase().getThreadIdIfExistsFor(recipient) return if (threadID < 0) null else threadID } override fun getThreadIdForMms(mmsId: Long): Long { val mmsDb = DatabaseComponent.get(context).mmsDatabase() val cursor = mmsDb.getMessage(mmsId) val reader = mmsDb.readerFor(cursor) val threadId = reader.next?.threadId cursor.close() return threadId ?: -1 } override fun getContactWithSessionID(sessionID: String): Contact? { return DatabaseComponent.get(context).sessionContactDatabase().getContactWithSessionID(sessionID) } override fun getAllContacts(): Set { return DatabaseComponent.get(context).sessionContactDatabase().getAllContacts() } override fun setContact(contact: Contact) { DatabaseComponent.get(context).sessionContactDatabase().setContact(contact) } override fun getRecipientForThread(threadId: Long): Recipient? { return DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(threadId) } override fun getRecipientSettings(address: Address): Recipient.RecipientSettings? { val recipientSettings = DatabaseComponent.get(context).recipientDatabase().getRecipientSettings(address) return if (recipientSettings.isPresent) { recipientSettings.get() } else null } override fun addContacts(contacts: List) { val recipientDatabase = DatabaseComponent.get(context).recipientDatabase() val threadDatabase = DatabaseComponent.get(context).threadDatabase() for (contact in contacts) { val address = Address.fromSerialized(contact.publicKey) val recipient = Recipient.from(context, address, true) if (!contact.profilePicture.isNullOrEmpty()) { recipientDatabase.setProfileAvatar(recipient, contact.profilePicture) } if (contact.profileKey?.isNotEmpty() == true) { recipientDatabase.setProfileKey(recipient, contact.profileKey) } if (contact.name.isNotEmpty()) { recipientDatabase.setProfileName(recipient, contact.name) } recipientDatabase.setProfileSharing(recipient, true) recipientDatabase.setRegistered(recipient, Recipient.RegisteredState.REGISTERED) // create Thread if needed threadDatabase.getOrCreateThreadIdFor(recipient) } if (contacts.isNotEmpty()) { threadDatabase.notifyConversationListListeners() } } override fun getLastUpdated(threadID: Long): Long { val threadDB = DatabaseComponent.get(context).threadDatabase() return threadDB.getLastUpdated(threadID) } override fun trimThread(threadID: Long, threadLimit: Int) { val threadDB = DatabaseComponent.get(context).threadDatabase() threadDB.trimThread(threadID, threadLimit) } override fun getAttachmentDataUri(attachmentId: AttachmentId): Uri { return PartAuthority.getAttachmentDataUri(attachmentId) } override fun getAttachmentThumbnailUri(attachmentId: AttachmentId): Uri { return PartAuthority.getAttachmentThumbnailUri(attachmentId) } override fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) { val database = DatabaseComponent.get(context).mmsDatabase() val address = fromSerialized(senderPublicKey) val recipient = Recipient.from(context, address, false) if (recipient.isBlocked) return val mediaMessage = IncomingMediaMessage(address, sentTimestamp, -1, 0, false, false, Optional.absent(), Optional.absent(), Optional.absent(), Optional.absent(), Optional.absent(), Optional.absent(), Optional.of(message)) database.insertSecureDecryptedMessageInbox(mediaMessage, -1) } }