mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-25 11:05:25 +00:00
feat: add storage with hashes and some basic profile update logic in config factory probably move that somewhere else
This commit is contained in:
parent
c8d520c3ce
commit
c639d57471
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.contentValuesOf
|
import androidx.core.content.contentValuesOf
|
||||||
import androidx.core.database.getBlobOrNull
|
import androidx.core.database.getBlobOrNull
|
||||||
|
import androidx.core.database.getStringOrNull
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||||
|
|
||||||
class ConfigDatabase(context: Context, helper: SQLCipherOpenHelper): Database(context, helper) {
|
class ConfigDatabase(context: Context, helper: SQLCipherOpenHelper): Database(context, helper) {
|
||||||
@ -21,24 +22,26 @@ class ConfigDatabase(context: Context, helper: SQLCipherOpenHelper): Database(co
|
|||||||
private const val VARIANT_AND_PUBKEY_WHERE = "$VARIANT = ? AND $PUBKEY = ?"
|
private const val VARIANT_AND_PUBKEY_WHERE = "$VARIANT = ? AND $PUBKEY = ?"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeConfig(variant: String, publicKey: String, data: ByteArray) {
|
fun storeConfig(variant: String, publicKey: String, data: ByteArray, hashes: List<String>) {
|
||||||
val db = writableDatabase
|
val db = writableDatabase
|
||||||
val contentValues = contentValuesOf(
|
val contentValues = contentValuesOf(
|
||||||
VARIANT to variant,
|
VARIANT to variant,
|
||||||
PUBKEY to publicKey,
|
PUBKEY to publicKey,
|
||||||
DATA to data
|
DATA to data,
|
||||||
|
COMBINED_MESSAGE_HASHES to hashes.joinToString(",")
|
||||||
)
|
)
|
||||||
db.insertOrUpdate(TABLE_NAME, contentValues, VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey))
|
db.insertOrUpdate(TABLE_NAME, contentValues, VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun retrieveConfig(variant: String, publicKey: String): ByteArray? {
|
fun retrieveConfigAndHashes(variant: String, publicKey: String): Pair<ByteArray,List<String>>? {
|
||||||
val db = readableDatabase
|
val db = readableDatabase
|
||||||
val query = db.query(TABLE_NAME, arrayOf(DATA), VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey),null, null, null)
|
val query = db.query(TABLE_NAME, arrayOf(DATA), VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey),null, null, null)
|
||||||
val bytes = query?.use { cursor ->
|
return query?.use { cursor ->
|
||||||
if (!cursor.moveToFirst()) return null
|
if (!cursor.moveToFirst()) return@use null
|
||||||
cursor.getBlobOrNull(cursor.getColumnIndex(DATA))
|
val bytes = cursor.getBlobOrNull(cursor.getColumnIndex(DATA)) ?: return@use null
|
||||||
|
val hashes = cursor.getStringOrNull(cursor.getColumnIndex(COMBINED_MESSAGE_HASHES))?.split(",") ?: emptyList()
|
||||||
|
bytes to hashes
|
||||||
}
|
}
|
||||||
return bytes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -7,11 +7,22 @@ import org.session.libsession.database.StorageProtocol
|
|||||||
import org.session.libsession.messaging.BlindedIdMapping
|
import org.session.libsession.messaging.BlindedIdMapping
|
||||||
import org.session.libsession.messaging.calls.CallMessageType
|
import org.session.libsession.messaging.calls.CallMessageType
|
||||||
import org.session.libsession.messaging.contacts.Contact
|
import org.session.libsession.messaging.contacts.Contact
|
||||||
import org.session.libsession.messaging.jobs.*
|
import org.session.libsession.messaging.jobs.AttachmentUploadJob
|
||||||
|
import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob
|
||||||
|
import org.session.libsession.messaging.jobs.Job
|
||||||
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
|
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
||||||
|
import org.session.libsession.messaging.jobs.MessageSendJob
|
||||||
import org.session.libsession.messaging.messages.Message
|
import org.session.libsession.messaging.messages.Message
|
||||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||||
import org.session.libsession.messaging.messages.signal.*
|
import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage
|
||||||
|
import org.session.libsession.messaging.messages.signal.IncomingGroupMessage
|
||||||
|
import org.session.libsession.messaging.messages.signal.IncomingMediaMessage
|
||||||
|
import org.session.libsession.messaging.messages.signal.IncomingTextMessage
|
||||||
|
import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage
|
||||||
|
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
|
||||||
|
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
|
||||||
import org.session.libsession.messaging.messages.visible.Attachment
|
import org.session.libsession.messaging.messages.visible.Attachment
|
||||||
import org.session.libsession.messaging.messages.visible.Profile
|
import org.session.libsession.messaging.messages.visible.Profile
|
||||||
import org.session.libsession.messaging.messages.visible.Reaction
|
import org.session.libsession.messaging.messages.visible.Reaction
|
||||||
@ -28,7 +39,7 @@ import org.session.libsession.messaging.utilities.SessionId
|
|||||||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||||
import org.session.libsession.messaging.utilities.UpdateMessageData
|
import org.session.libsession.messaging.utilities.UpdateMessageData
|
||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
import org.session.libsession.utilities.*
|
import org.session.libsession.utilities.Address
|
||||||
import org.session.libsession.utilities.Address.Companion.fromSerialized
|
import org.session.libsession.utilities.Address.Companion.fromSerialized
|
||||||
import org.session.libsession.utilities.GroupRecord
|
import org.session.libsession.utilities.GroupRecord
|
||||||
import org.session.libsession.utilities.GroupUtil
|
import org.session.libsession.utilities.GroupUtil
|
||||||
@ -70,13 +81,12 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
return Profile(displayName, profileKey, profilePictureUrl)
|
return Profile(displayName, profileKey, profilePictureUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setUserProfilePictureURL(newValue: String) {
|
override fun setUserProfilePictureURL(newProfilePicture: String?) {
|
||||||
val ourRecipient = fromSerialized(getUserPublicKey()!!).let {
|
val ourRecipient = fromSerialized(getUserPublicKey()!!).let {
|
||||||
Recipient.from(context, it, false)
|
Recipient.from(context, it, false)
|
||||||
}
|
}
|
||||||
TextSecurePreferences.setProfilePictureURL(context, newValue)
|
TextSecurePreferences.setProfilePictureURL(context, newProfilePicture)
|
||||||
RetrieveProfileAvatarJob(ourRecipient, newValue)
|
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, newProfilePicture))
|
||||||
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, newValue))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getOrGenerateRegistrationID(): Int {
|
override fun getOrGenerateRegistrationID(): Int {
|
||||||
|
@ -1,13 +1,26 @@
|
|||||||
package org.thoughtcrime.securesms.dependencies
|
package org.thoughtcrime.securesms.dependencies
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import network.loki.messenger.libsession_util.ConfigBase
|
||||||
import network.loki.messenger.libsession_util.Contacts
|
import network.loki.messenger.libsession_util.Contacts
|
||||||
import network.loki.messenger.libsession_util.ConversationVolatileConfig
|
import network.loki.messenger.libsession_util.ConversationVolatileConfig
|
||||||
import network.loki.messenger.libsession_util.UserProfile
|
import network.loki.messenger.libsession_util.UserProfile
|
||||||
|
import org.session.libsession.database.StorageProtocol
|
||||||
|
import org.session.libsession.utilities.Address
|
||||||
import org.session.libsession.utilities.ConfigFactoryProtocol
|
import org.session.libsession.utilities.ConfigFactoryProtocol
|
||||||
|
import org.session.libsession.utilities.ProfileKeyUtil
|
||||||
|
import org.session.libsession.utilities.SSKEnvironment
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage
|
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage
|
||||||
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.thoughtcrime.securesms.database.ConfigDatabase
|
import org.thoughtcrime.securesms.database.ConfigDatabase
|
||||||
|
import java.util.concurrent.ConcurrentSkipListSet
|
||||||
|
|
||||||
class ConfigFactory(private val configDatabase: ConfigDatabase, private val maybeGetUserInfo: ()->Pair<ByteArray, String>?):
|
class ConfigFactory(private val context: Context,
|
||||||
|
private val configDatabase: ConfigDatabase,
|
||||||
|
private val storage: StorageProtocol,
|
||||||
|
private val maybeGetUserInfo: ()->Pair<ByteArray, String>?):
|
||||||
ConfigFactoryProtocol {
|
ConfigFactoryProtocol {
|
||||||
|
|
||||||
fun keyPairChanged() { // this should only happen restoring or clearing data
|
fun keyPairChanged() { // this should only happen restoring or clearing data
|
||||||
@ -21,17 +34,23 @@ class ConfigFactory(private val configDatabase: ConfigDatabase, private val mayb
|
|||||||
|
|
||||||
private val userLock = Object()
|
private val userLock = Object()
|
||||||
private var _userConfig: UserProfile? = null
|
private var _userConfig: UserProfile? = null
|
||||||
private val contactLock = Object()
|
private val userHashes = ConcurrentSkipListSet<String>()
|
||||||
|
private val contactsLock = Object()
|
||||||
private var _contacts: Contacts? = null
|
private var _contacts: Contacts? = null
|
||||||
|
private val contactsHashes = ConcurrentSkipListSet<String>()
|
||||||
private val convoVolatileLock = Object()
|
private val convoVolatileLock = Object()
|
||||||
private var _convoVolatileConfig: ConversationVolatileConfig? = null
|
private var _convoVolatileConfig: ConversationVolatileConfig? = null
|
||||||
|
private val convoHashes = ConcurrentSkipListSet<String>()
|
||||||
|
|
||||||
override val user: UserProfile? = synchronized(userLock) {
|
override val user: UserProfile? = synchronized(userLock) {
|
||||||
if (_userConfig == null) {
|
if (_userConfig == null) {
|
||||||
val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null
|
val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null
|
||||||
val userDump = configDatabase.retrieveConfig(SharedConfigMessage.Kind.USER_PROFILE.name, publicKey)
|
val userDump = configDatabase.retrieveConfigAndHashes(SharedConfigMessage.Kind.USER_PROFILE.name, publicKey)
|
||||||
_userConfig = if (userDump != null) {
|
_userConfig = if (userDump != null) {
|
||||||
UserProfile.newInstance(secretKey, userDump)
|
val (bytes, hashes) = userDump
|
||||||
|
userHashes.clear()
|
||||||
|
userHashes.addAll(hashes)
|
||||||
|
UserProfile.newInstance(secretKey, bytes)
|
||||||
} else {
|
} else {
|
||||||
UserProfile.newInstance(secretKey)
|
UserProfile.newInstance(secretKey)
|
||||||
}
|
}
|
||||||
@ -39,12 +58,15 @@ class ConfigFactory(private val configDatabase: ConfigDatabase, private val mayb
|
|||||||
_userConfig
|
_userConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
override val contacts: Contacts? = synchronized(contactLock) {
|
override val contacts: Contacts? = synchronized(contactsLock) {
|
||||||
if (_contacts == null) {
|
if (_contacts == null) {
|
||||||
val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null
|
val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null
|
||||||
val contactsDump = configDatabase.retrieveConfig(SharedConfigMessage.Kind.CONTACTS.name, publicKey)
|
val contactsDump = configDatabase.retrieveConfigAndHashes(SharedConfigMessage.Kind.CONTACTS.name, publicKey)
|
||||||
_contacts = if (contactsDump != null) {
|
_contacts = if (contactsDump != null) {
|
||||||
Contacts.newInstance(secretKey, contactsDump)
|
val (bytes, hashes) = contactsDump
|
||||||
|
contactsHashes.clear()
|
||||||
|
contactsHashes.addAll(hashes)
|
||||||
|
Contacts.newInstance(secretKey, bytes)
|
||||||
} else {
|
} else {
|
||||||
Contacts.newInstance(secretKey)
|
Contacts.newInstance(secretKey)
|
||||||
}
|
}
|
||||||
@ -55,9 +77,12 @@ class ConfigFactory(private val configDatabase: ConfigDatabase, private val mayb
|
|||||||
override val convoVolatile: ConversationVolatileConfig? = synchronized(convoVolatileLock) {
|
override val convoVolatile: ConversationVolatileConfig? = synchronized(convoVolatileLock) {
|
||||||
if (_convoVolatileConfig == null) {
|
if (_convoVolatileConfig == null) {
|
||||||
val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null
|
val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null
|
||||||
val convoDump = configDatabase.retrieveConfig(SharedConfigMessage.Kind.CONVO_INFO_VOLATILE.name, publicKey)
|
val convoDump = configDatabase.retrieveConfigAndHashes(SharedConfigMessage.Kind.CONVO_INFO_VOLATILE.name, publicKey)
|
||||||
_convoVolatileConfig = if (convoDump != null) {
|
_convoVolatileConfig = if (convoDump != null) {
|
||||||
ConversationVolatileConfig.newInstance(secretKey, convoDump)
|
val (bytes, hashes) = convoDump
|
||||||
|
convoHashes.clear()
|
||||||
|
convoHashes.addAll(hashes)
|
||||||
|
ConversationVolatileConfig.newInstance(secretKey, bytes)
|
||||||
} else {
|
} else {
|
||||||
ConversationVolatileConfig.newInstance(secretKey)
|
ConversationVolatileConfig.newInstance(secretKey)
|
||||||
}
|
}
|
||||||
@ -66,21 +91,84 @@ class ConfigFactory(private val configDatabase: ConfigDatabase, private val mayb
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun saveUserConfigDump() {
|
private fun persistUserConfigDump() = synchronized(userLock) {
|
||||||
val dumped = user?.dump() ?: return
|
val dumped = user?.dump() ?: return
|
||||||
val (_, publicKey) = maybeGetUserInfo() ?: return
|
val (_, publicKey) = maybeGetUserInfo() ?: return
|
||||||
configDatabase.storeConfig(SharedConfigMessage.Kind.USER_PROFILE.name, publicKey, dumped)
|
configDatabase.storeConfig(SharedConfigMessage.Kind.USER_PROFILE.name, publicKey, dumped, userHashes.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveContactConfigDump() {
|
private fun persistContactsConfigDump() = synchronized(contactsLock) {
|
||||||
val dumped = contacts?.dump() ?: return
|
val dumped = contacts?.dump() ?: return
|
||||||
val (_, publicKey) = maybeGetUserInfo() ?: return
|
val (_, publicKey) = maybeGetUserInfo() ?: return
|
||||||
configDatabase.storeConfig(SharedConfigMessage.Kind.CONTACTS.name, publicKey, dumped)
|
configDatabase.storeConfig(SharedConfigMessage.Kind.CONTACTS.name, publicKey, dumped, contactsHashes.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveConvoVolatileConfigDump() {
|
private fun persistConvoVolatileConfigDump() = synchronized (convoVolatileLock) {
|
||||||
val dumped = convoVolatile?.dump() ?: return
|
val dumped = convoVolatile?.dump() ?: return
|
||||||
val (_, publicKey) = maybeGetUserInfo() ?: return
|
val (_, publicKey) = maybeGetUserInfo() ?: return
|
||||||
configDatabase.storeConfig(SharedConfigMessage.Kind.CONVO_INFO_VOLATILE.name, publicKey, dumped)
|
configDatabase.storeConfig(SharedConfigMessage.Kind.CONVO_INFO_VOLATILE.name, publicKey, dumped, convoHashes.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun persist(forConfigObject: ConfigBase) {
|
||||||
|
when (forConfigObject) {
|
||||||
|
is UserProfile -> persistUserConfigDump()
|
||||||
|
is Contacts -> persistContactsConfigDump()
|
||||||
|
is ConversationVolatileConfig -> persistConvoVolatileConfigDump()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun appendHash(configObject: ConfigBase, hash: String) {
|
||||||
|
when (configObject) {
|
||||||
|
is UserProfile -> userHashes.add(hash)
|
||||||
|
is Contacts -> contactsHashes.add(hash)
|
||||||
|
is ConversationVolatileConfig -> convoHashes.add(hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun notifyUpdates(forConfigObject: ConfigBase) {
|
||||||
|
when (forConfigObject) {
|
||||||
|
is UserProfile -> updateUser(forConfigObject)
|
||||||
|
is Contacts -> updateContacts(forConfigObject)
|
||||||
|
is ConversationVolatileConfig -> updateConvoVolatile(forConfigObject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUser(userProfile: UserProfile) {
|
||||||
|
val (_, userPublicKey) = maybeGetUserInfo() ?: return
|
||||||
|
// would love to get rid of recipient and context from this
|
||||||
|
val recipient = Recipient.from(context, Address.fromSerialized(userPublicKey), false)
|
||||||
|
// update name
|
||||||
|
val name = userProfile.getName() ?: return
|
||||||
|
val userPic = userProfile.getPic()
|
||||||
|
val profileManager = SSKEnvironment.shared.profileManager
|
||||||
|
if (name.isNotEmpty()) {
|
||||||
|
TextSecurePreferences.setProfileName(context, name)
|
||||||
|
profileManager.setName(context, recipient, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update pfp
|
||||||
|
if (userPic == null) {
|
||||||
|
// clear picture if userPic is null
|
||||||
|
TextSecurePreferences.setProfileKey(context, null)
|
||||||
|
ProfileKeyUtil.setEncodedProfileKey(context, null)
|
||||||
|
profileManager.setProfileKey(context, recipient, null)
|
||||||
|
storage.setUserProfilePictureURL(null)
|
||||||
|
} else if (userPic.key.isNotEmpty() && userPic.url.isNotEmpty()
|
||||||
|
&& TextSecurePreferences.getProfilePictureURL(context) != userPic.url) {
|
||||||
|
val profileKey = Base64.encodeBytes(userPic.key)
|
||||||
|
ProfileKeyUtil.setEncodedProfileKey(context, profileKey)
|
||||||
|
profileManager.setProfileKey(context, recipient, userPic.key)
|
||||||
|
storage.setUserProfilePictureURL(userPic.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateContacts(contacts: Contacts) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateConvoVolatile(convos: ConversationVolatileConfig) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -6,6 +6,7 @@ import dagger.Provides
|
|||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import org.session.libsession.database.StorageProtocol
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
||||||
import org.thoughtcrime.securesms.database.ConfigDatabase
|
import org.thoughtcrime.securesms.database.ConfigDatabase
|
||||||
@ -22,8 +23,8 @@ object SessionUtilModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideConfigFactory(@ApplicationContext context: Context, configDatabase: ConfigDatabase): ConfigFactory =
|
fun provideConfigFactory(@ApplicationContext context: Context, configDatabase: ConfigDatabase, storage: StorageProtocol): ConfigFactory =
|
||||||
ConfigFactory(configDatabase) {
|
ConfigFactory(context, configDatabase, storage) {
|
||||||
val localUserPublicKey = TextSecurePreferences.getLocalNumber(context)
|
val localUserPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
val secretKey = maybeUserEdSecretKey(context)
|
val secretKey = maybeUserEdSecretKey(context)
|
||||||
if (localUserPublicKey == null || secretKey == null) null
|
if (localUserPublicKey == null || secretKey == null) null
|
||||||
|
@ -54,7 +54,7 @@ class ProfileManager : SSKEnvironment.ProfileManagerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray) {
|
override fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray?) {
|
||||||
// New API
|
// New API
|
||||||
val sessionID = recipient.address.serialize()
|
val sessionID = recipient.address.serialize()
|
||||||
val contactDatabase = DatabaseComponent.get(context).sessionContactDatabase()
|
val contactDatabase = DatabaseComponent.get(context).sessionContactDatabase()
|
||||||
|
@ -7,56 +7,69 @@ import network.loki.messenger.libsession_util.util.Contact
|
|||||||
import network.loki.messenger.libsession_util.util.UserPic
|
import network.loki.messenger.libsession_util.util.UserPic
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
|
import org.session.libsession.messaging.messages.Destination
|
||||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||||
|
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.Address
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
|
||||||
object ConfigurationMessageUtilities {
|
object ConfigurationMessageUtilities {
|
||||||
|
|
||||||
|
const val isNewConfigEnabled = true
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun syncConfigurationIfNeeded(context: Context) {
|
fun syncConfigurationIfNeeded(context: Context) {
|
||||||
return
|
// add if check here to schedule new config job process and return early
|
||||||
// val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
|
if (isNewConfigEnabled) {
|
||||||
// val lastSyncTime = TextSecurePreferences.getLastConfigurationSyncTime(context)
|
// schedule job if none exist
|
||||||
// val now = System.currentTimeMillis()
|
TODO()
|
||||||
// if (now - lastSyncTime < 7 * 24 * 60 * 60 * 1000) return
|
}
|
||||||
// val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
|
||||||
// !recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
|
val lastSyncTime = TextSecurePreferences.getLastConfigurationSyncTime(context)
|
||||||
// }.map { recipient ->
|
val now = System.currentTimeMillis()
|
||||||
// ConfigurationMessage.Contact(
|
if (now - lastSyncTime < 7 * 24 * 60 * 60 * 1000) return
|
||||||
// publicKey = recipient.address.serialize(),
|
val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
|
||||||
// name = recipient.name!!,
|
!recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
|
||||||
// profilePicture = recipient.profileAvatar,
|
}.map { recipient ->
|
||||||
// profileKey = recipient.profileKey,
|
ConfigurationMessage.Contact(
|
||||||
// isApproved = recipient.isApproved,
|
publicKey = recipient.address.serialize(),
|
||||||
// isBlocked = recipient.isBlocked,
|
name = recipient.name!!,
|
||||||
// didApproveMe = recipient.hasApprovedMe()
|
profilePicture = recipient.profileAvatar,
|
||||||
// )
|
profileKey = recipient.profileKey,
|
||||||
// }
|
isApproved = recipient.isApproved,
|
||||||
// val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return
|
isBlocked = recipient.isBlocked,
|
||||||
// MessageSender.send(configurationMessage, Address.fromSerialized(userPublicKey))
|
didApproveMe = recipient.hasApprovedMe()
|
||||||
// TextSecurePreferences.setLastConfigurationSyncTime(context, now)
|
)
|
||||||
|
}
|
||||||
|
val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return
|
||||||
|
MessageSender.send(configurationMessage, Address.fromSerialized(userPublicKey))
|
||||||
|
TextSecurePreferences.setLastConfigurationSyncTime(context, now)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forceSyncConfigurationNowIfNeeded(context: Context): Promise<Unit, Exception> {
|
fun forceSyncConfigurationNowIfNeeded(context: Context): Promise<Unit, Exception> {
|
||||||
return Promise.ofSuccess(Unit)
|
// add if check here to schedule new config job process and return early
|
||||||
// val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Promise.ofSuccess(Unit)
|
if (isNewConfigEnabled) {
|
||||||
// val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
|
// schedule job if none exist
|
||||||
// !recipient.isGroupRecipient && !recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
|
TODO()
|
||||||
// }.map { recipient ->
|
}
|
||||||
// ConfigurationMessage.Contact(
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Promise.ofSuccess(Unit)
|
||||||
// publicKey = recipient.address.serialize(),
|
val contacts = ContactUtilities.getAllContacts(context).filter { recipient ->
|
||||||
// name = recipient.name!!,
|
!recipient.isGroupRecipient && !recipient.name.isNullOrEmpty() && !recipient.isLocalNumber && recipient.address.serialize().isNotEmpty()
|
||||||
// profilePicture = recipient.profileAvatar,
|
}.map { recipient ->
|
||||||
// profileKey = recipient.profileKey,
|
ConfigurationMessage.Contact(
|
||||||
// isApproved = recipient.isApproved,
|
publicKey = recipient.address.serialize(),
|
||||||
// isBlocked = recipient.isBlocked,
|
name = recipient.name!!,
|
||||||
// didApproveMe = recipient.hasApprovedMe()
|
profilePicture = recipient.profileAvatar,
|
||||||
// )
|
profileKey = recipient.profileKey,
|
||||||
// }
|
isApproved = recipient.isApproved,
|
||||||
// val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return Promise.ofSuccess(Unit)
|
isBlocked = recipient.isBlocked,
|
||||||
// val promise = MessageSender.send(configurationMessage, Destination.from(Address.fromSerialized(userPublicKey)))
|
didApproveMe = recipient.hasApprovedMe()
|
||||||
// TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis())
|
)
|
||||||
// return promise
|
}
|
||||||
|
val configurationMessage = ConfigurationMessage.getCurrent(contacts) ?: return Promise.ofSuccess(Unit)
|
||||||
|
val promise = MessageSender.send(configurationMessage, Destination.from(Address.fromSerialized(userPublicKey)))
|
||||||
|
TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis())
|
||||||
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeUserSecretKey() = MessagingModuleConfiguration.shared.getUserED25519KeyPair()?.secretKey?.asBytes
|
private fun maybeUserSecretKey() = MessagingModuleConfiguration.shared.getUserED25519KeyPair()?.secretKey?.asBytes
|
||||||
@ -110,7 +123,7 @@ object ConfigurationMessageUtilities {
|
|||||||
return dump
|
return dump
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generateConversationDump(context: Context): ByteArray? {
|
fun generateConversationVolatileDump(context: Context): ByteArray? {
|
||||||
TODO()
|
TODO()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ interface StorageProtocol {
|
|||||||
fun getUserPublicKey(): String?
|
fun getUserPublicKey(): String?
|
||||||
fun getUserX25519KeyPair(): ECKeyPair
|
fun getUserX25519KeyPair(): ECKeyPair
|
||||||
fun getUserProfile(): Profile
|
fun getUserProfile(): Profile
|
||||||
fun setUserProfilePictureURL(newProfilePicture: String)
|
fun setUserProfilePictureURL(newProfilePicture: String?)
|
||||||
// Signal
|
// Signal
|
||||||
fun getOrGenerateRegistrationID(): Int
|
fun getOrGenerateRegistrationID(): Int
|
||||||
|
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package org.session.libsession.messaging.messages.control
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString
|
||||||
|
import org.session.libsignal.protos.SignalServiceProtos
|
||||||
|
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage
|
||||||
|
|
||||||
|
class SharedConfigurationMessage(val kind: SharedConfigMessage.Kind, val data: ByteArray, val seqNo: Long): ControlMessage() {
|
||||||
|
|
||||||
|
override val ttl: Long = 30 * 24 * 60 * 60 * 1000L
|
||||||
|
override val isSelfSendValid: Boolean = true
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromProto(proto: SignalServiceProtos.Content): SharedConfigurationMessage? {
|
||||||
|
if (!proto.hasSharedConfigMessage()) return null
|
||||||
|
val sharedConfig = proto.sharedConfigMessage
|
||||||
|
if (!sharedConfig.hasKind() || !sharedConfig.hasData()) return null
|
||||||
|
return SharedConfigurationMessage(sharedConfig.kind, sharedConfig.data.toByteArray(), sharedConfig.seqno)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
if (!super.isValid()) return false
|
||||||
|
return data.isNotEmpty() && seqNo >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toProto(): SignalServiceProtos.Content? {
|
||||||
|
val sharedConfigurationMessage = SharedConfigMessage.newBuilder()
|
||||||
|
.setKind(kind)
|
||||||
|
.setSeqno(seqNo)
|
||||||
|
.setData(ByteString.copyFrom(data))
|
||||||
|
.build()
|
||||||
|
return SignalServiceProtos.Content.newBuilder()
|
||||||
|
.setSharedConfigMessage(sharedConfigurationMessage)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import org.session.libsession.messaging.messages.control.DataExtractionNotificat
|
|||||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
||||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||||
import org.session.libsession.messaging.messages.control.ReadReceipt
|
import org.session.libsession.messaging.messages.control.ReadReceipt
|
||||||
|
import org.session.libsession.messaging.messages.control.SharedConfigurationMessage
|
||||||
import org.session.libsession.messaging.messages.control.TypingIndicator
|
import org.session.libsession.messaging.messages.control.TypingIndicator
|
||||||
import org.session.libsession.messaging.messages.control.UnsendRequest
|
import org.session.libsession.messaging.messages.control.UnsendRequest
|
||||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||||
@ -138,6 +139,7 @@ object MessageReceiver {
|
|||||||
UnsendRequest.fromProto(proto) ?:
|
UnsendRequest.fromProto(proto) ?:
|
||||||
MessageRequestResponse.fromProto(proto) ?:
|
MessageRequestResponse.fromProto(proto) ?:
|
||||||
CallMessage.fromProto(proto) ?:
|
CallMessage.fromProto(proto) ?:
|
||||||
|
SharedConfigurationMessage.fromProto(proto) ?:
|
||||||
VisibleMessage.fromProto(proto) ?: run {
|
VisibleMessage.fromProto(proto) ?: run {
|
||||||
throw Error.UnknownMessage
|
throw Error.UnknownMessage
|
||||||
}
|
}
|
||||||
@ -166,12 +168,13 @@ object MessageReceiver {
|
|||||||
// If the message failed to process the first time around we retry it later (if the error is retryable). In this case the timestamp
|
// If the message failed to process the first time around we retry it later (if the error is retryable). In this case the timestamp
|
||||||
// will already be in the database but we don't want to treat the message as a duplicate. The isRetry flag is a simple workaround
|
// will already be in the database but we don't want to treat the message as a duplicate. The isRetry flag is a simple workaround
|
||||||
// for this issue.
|
// for this issue.
|
||||||
if (message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) {
|
if ((message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) || message is SharedConfigurationMessage) {
|
||||||
// Allow duplicates in this case to avoid the following situation:
|
// Allow duplicates in this case to avoid the following situation:
|
||||||
// • The app performed a background poll or received a push notification
|
// • The app performed a background poll or received a push notification
|
||||||
// • This method was invoked and the received message timestamps table was updated
|
// • This method was invoked and the received message timestamps table was updated
|
||||||
// • Processing wasn't finished
|
// • Processing wasn't finished
|
||||||
// • The user doesn't see the new closed group
|
// • The user doesn't see the new closed group
|
||||||
|
// also allow shared configuration messages to be duplicates since we track hashes separately use seqno for conflict resolution
|
||||||
} else {
|
} else {
|
||||||
if (storage.isDuplicateMessage(envelope.timestamp)) { throw Error.DuplicateMessage }
|
if (storage.isDuplicateMessage(envelope.timestamp)) { throw Error.DuplicateMessage }
|
||||||
storage.addReceivedMessageTimestamp(envelope.timestamp)
|
storage.addReceivedMessageTimestamp(envelope.timestamp)
|
||||||
|
@ -7,6 +7,8 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import network.loki.messenger.libsession_util.ConfigBase
|
import network.loki.messenger.libsession_util.ConfigBase
|
||||||
|
import network.loki.messenger.libsession_util.Contacts
|
||||||
|
import network.loki.messenger.libsession_util.ConversationVolatileConfig
|
||||||
import network.loki.messenger.libsession_util.UserProfile
|
import network.loki.messenger.libsession_util.UserProfile
|
||||||
import nl.komponents.kovenant.Deferred
|
import nl.komponents.kovenant.Deferred
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
@ -18,6 +20,8 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
|
|||||||
import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
|
import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
|
||||||
import org.session.libsession.messaging.jobs.JobQueue
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
import org.session.libsession.messaging.jobs.MessageReceiveParameters
|
import org.session.libsession.messaging.jobs.MessageReceiveParameters
|
||||||
|
import org.session.libsession.messaging.messages.control.SharedConfigurationMessage
|
||||||
|
import org.session.libsession.messaging.sending_receiving.MessageReceiver
|
||||||
import org.session.libsession.snode.RawResponse
|
import org.session.libsession.snode.RawResponse
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsession.snode.SnodeModule
|
import org.session.libsession.snode.SnodeModule
|
||||||
@ -121,18 +125,36 @@ class Poller(private val configFactory: ConfigFactoryProtocol) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processUserConfig(snode: Snode, rawMessages: RawResponse) {
|
private fun processConfig(snode: Snode, rawMessages: RawResponse, namespace: Int, forConfigObject: ConfigBase?) {
|
||||||
SnodeAPI.parseRawMessagesResponse(rawMessages, snode, userPublicKey)
|
if (forConfigObject == null) return
|
||||||
|
|
||||||
|
val messages = SnodeAPI.parseRawMessagesResponse(
|
||||||
|
rawMessages,
|
||||||
|
snode,
|
||||||
|
userPublicKey,
|
||||||
|
namespace,
|
||||||
|
updateLatestHash = false
|
||||||
|
)
|
||||||
|
messages.forEach { (envelope, hash) ->
|
||||||
|
try {
|
||||||
|
val (message, _) = MessageReceiver.parse(data = envelope.toByteArray(), openGroupServerID = null)
|
||||||
|
// sanity checks
|
||||||
|
if (message !is SharedConfigurationMessage) {
|
||||||
|
Log.w("Loki-DBG", "shared config message handled in configs wasn't SharedConfigurationMessage but was ${message.javaClass.simpleName}")
|
||||||
|
return@forEach
|
||||||
}
|
}
|
||||||
|
// maybe do something with seqNo ?
|
||||||
private fun processContactsConfig(snode: Snode, rawMessages: RawResponse) {
|
Log.d("Loki-DBG", "Merging config of kind ${message.kind} into ${forConfigObject.javaClass.simpleName}")
|
||||||
|
forConfigObject.merge(message.data)
|
||||||
|
configFactory.appendHash(forConfigObject, hash!!)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("Loki", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processConvoVolatileConfig(snode: Snode, rawMessages: RawResponse) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// process new results
|
||||||
|
configFactory.persist(forConfigObject)
|
||||||
|
configFactory.notifyUpdates(forConfigObject)
|
||||||
|
}
|
||||||
|
|
||||||
private fun poll(snode: Snode, deferred: Deferred<Unit, Exception>): Promise<Unit, Exception> {
|
private fun poll(snode: Snode, deferred: Deferred<Unit, Exception>): Promise<Unit, Exception> {
|
||||||
if (!hasStarted) { return Promise.ofFail(PromiseCanceledException()) }
|
if (!hasStarted) { return Promise.ofFail(PromiseCanceledException()) }
|
||||||
@ -160,15 +182,20 @@ class Poller(private val configFactory: ConfigFactoryProtocol) {
|
|||||||
if (deferred.promise.isDone()) {
|
if (deferred.promise.isDone()) {
|
||||||
return@bind Promise.ofSuccess(Unit)
|
return@bind Promise.ofSuccess(Unit)
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: remove log after testing responses
|
||||||
Log.d("Loki-DBG", JsonUtil.toJson(rawResponses))
|
Log.d("Loki-DBG", JsonUtil.toJson(rawResponses))
|
||||||
val requestList = (rawResponses["results"] as List<RawResponse>)
|
val requestList = (rawResponses["results"] as List<RawResponse>)
|
||||||
|
// in case we had null configs, the array won't be fully populated
|
||||||
|
// index of the sparse array key iterator should be the request index, with the key being the namespace
|
||||||
requestSparseArray.keyIterator().withIndex().forEach { (requestIndex, key) ->
|
requestSparseArray.keyIterator().withIndex().forEach { (requestIndex, key) ->
|
||||||
requestList.getOrNull(requestIndex)?.let { rawResponse ->
|
requestList.getOrNull(requestIndex)?.let { rawResponse ->
|
||||||
if (key == Namespace.DEFAULT) {
|
if (key == Namespace.DEFAULT) {
|
||||||
processPersonalMessages(snode, rawResponse)
|
processPersonalMessages(snode, rawResponse)
|
||||||
} else {
|
} else {
|
||||||
when (ConfigBase.kindFor(key)) {
|
when (ConfigBase.kindFor(key)) {
|
||||||
UserProfile ->
|
UserProfile::class.java -> processConfig(snode, rawResponse, key, configFactory.user)
|
||||||
|
Contacts::class.java -> processConfig(snode, rawResponse, key, configFactory.contacts)
|
||||||
|
ConversationVolatileConfig::class.java -> processConfig(snode, rawResponse, key, configFactory.convoVolatile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -628,10 +628,12 @@ object SnodeAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String, namespace: Int = 0): List<Pair<SignalServiceProtos.Envelope, String?>> {
|
fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String, namespace: Int = 0, updateLatestHash: Boolean = true): List<Pair<SignalServiceProtos.Envelope, String?>> {
|
||||||
val messages = rawResponse["messages"] as? List<*>
|
val messages = rawResponse["messages"] as? List<*>
|
||||||
return if (messages != null) {
|
return if (messages != null) {
|
||||||
|
if (updateLatestHash) {
|
||||||
updateLastMessageHashValueIfPossible(snode, publicKey, messages, namespace)
|
updateLastMessageHashValueIfPossible(snode, publicKey, messages, namespace)
|
||||||
|
}
|
||||||
val newRawMessages = removeDuplicates(publicKey, messages, namespace)
|
val newRawMessages = removeDuplicates(publicKey, messages, namespace)
|
||||||
return parseEnvelopes(newRawMessages)
|
return parseEnvelopes(newRawMessages)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.session.libsession.utilities
|
package org.session.libsession.utilities
|
||||||
|
|
||||||
|
import network.loki.messenger.libsession_util.ConfigBase
|
||||||
import network.loki.messenger.libsession_util.Contacts
|
import network.loki.messenger.libsession_util.Contacts
|
||||||
import network.loki.messenger.libsession_util.ConversationVolatileConfig
|
import network.loki.messenger.libsession_util.ConversationVolatileConfig
|
||||||
import network.loki.messenger.libsession_util.UserProfile
|
import network.loki.messenger.libsession_util.UserProfile
|
||||||
@ -8,7 +9,7 @@ interface ConfigFactoryProtocol {
|
|||||||
val user: UserProfile?
|
val user: UserProfile?
|
||||||
val contacts: Contacts?
|
val contacts: Contacts?
|
||||||
val convoVolatile: ConversationVolatileConfig?
|
val convoVolatile: ConversationVolatileConfig?
|
||||||
fun saveUserConfigDump()
|
fun persist(forConfigObject: ConfigBase)
|
||||||
fun saveContactConfigDump()
|
fun appendHash(configObject: ConfigBase, hash: String)
|
||||||
fun saveConvoVolatileConfigDump()
|
fun notifyUpdates(forConfigObject: ConfigBase)
|
||||||
}
|
}
|
@ -3,7 +3,6 @@ package org.session.libsession.utilities
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
||||||
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier
|
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier
|
||||||
import org.session.libsession.utilities.Address
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
|
||||||
class SSKEnvironment(
|
class SSKEnvironment(
|
||||||
@ -32,7 +31,7 @@ class SSKEnvironment(
|
|||||||
fun setNickname(context: Context, recipient: Recipient, nickname: String?)
|
fun setNickname(context: Context, recipient: Recipient, nickname: String?)
|
||||||
fun setName(context: Context, recipient: Recipient, name: String)
|
fun setName(context: Context, recipient: Recipient, name: String)
|
||||||
fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String)
|
fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String)
|
||||||
fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray)
|
fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray?)
|
||||||
fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode)
|
fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user