feat: finish most wrappers and compile issues, start new closed group compose code

pull/1403/head
0x330a 2 years ago
parent 993a2c4a64
commit 76ae6997db
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C

@ -19,7 +19,7 @@ object MentionManagerUtilities {
} }
recipient.address.isClosedGroup -> { recipient.address.isClosedGroup -> {
val members = storage.getMembers(recipient.address.serialize()) val members = storage.getMembers(recipient.address.serialize())
TODO("Fix when compile errors are dealt with for recipient closed groups") result.addAll(members.map { it.sessionId })
} }
recipient.address.isOpenGroup -> { recipient.address.isOpenGroup -> {
val messageDatabase = DatabaseComponent.get(context).mmsSmsDatabase() val messageDatabase = DatabaseComponent.get(context).mmsSmsDatabase()

@ -10,7 +10,7 @@ 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.GroupInfoConfig import network.loki.messenger.libsession_util.GroupInfoConfig
import network.loki.messenger.libsession_util.GroupKeysConfig import network.loki.messenger.libsession_util.GroupKeysConfig
import network.loki.messenger.libsession_util.GroupMemberConfig import network.loki.messenger.libsession_util.GroupMembersConfig
import network.loki.messenger.libsession_util.UserGroupsConfig import network.loki.messenger.libsession_util.UserGroupsConfig
import network.loki.messenger.libsession_util.UserProfile import network.loki.messenger.libsession_util.UserProfile
import network.loki.messenger.libsession_util.util.BaseCommunityInfo import network.loki.messenger.libsession_util.util.BaseCommunityInfo
@ -94,6 +94,7 @@ import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.SessionMetaProtocol import org.thoughtcrime.securesms.util.SessionMetaProtocol
import java.security.MessageDigest import java.security.MessageDigest
import network.loki.messenger.libsession_util.util.Contact as LibSessionContact import network.loki.messenger.libsession_util.util.Contact as LibSessionContact
import network.loki.messenger.libsession_util.util.GroupMember as LibSessionGroupMember
open class Storage(context: Context, helper: SQLCipherOpenHelper, private val configFactory: ConfigFactory) : Database(context, helper), StorageProtocol, open class Storage(context: Context, helper: SQLCipherOpenHelper, private val configFactory: ConfigFactory) : Database(context, helper), StorageProtocol,
ThreadDatabase.ConversationThreadUpdateListener { ThreadDatabase.ConversationThreadUpdateListener {
@ -460,9 +461,9 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
is Contacts -> updateContacts(forConfigObject) is Contacts -> updateContacts(forConfigObject)
is ConversationVolatileConfig -> updateConvoVolatile(forConfigObject) is ConversationVolatileConfig -> updateConvoVolatile(forConfigObject)
is UserGroupsConfig -> updateUserGroups(forConfigObject) is UserGroupsConfig -> updateUserGroups(forConfigObject)
is GroupInfoConfig -> TODO() is GroupInfoConfig -> updateGroupInfo(forConfigObject)
is GroupKeysConfig -> TODO() is GroupKeysConfig -> updateGroupKeys(forConfigObject)
is GroupMemberConfig -> TODO() is GroupMembersConfig -> updateGroupMembers(forConfigObject)
} }
} }
@ -499,6 +500,19 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
} }
private fun updateGroupInfo(groupInfoConfig: GroupInfoConfig) {
val threadId = getOrCreateThreadIdFor(Address.fromSerialized(groupInfoConfig.id().hexString()))
// TODO: handle deleted group, handle delete attachment / message before a certain time
}
private fun updateGroupKeys(groupKeys: GroupKeysConfig) {
// TODO: update something here?
}
private fun updateGroupMembers(groupMembers: GroupMembersConfig) {
// TODO: maybe clear out some contacts or something?
}
private fun updateContacts(contacts: Contacts) { private fun updateContacts(contacts: Contacts) {
val extracted = contacts.all().toList() val extracted = contacts.all().toList()
addLibSessionContacts(extracted) addLibSessionContacts(extracted)
@ -1030,10 +1044,8 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
} }
} }
override fun getMembers(groupPublicKey: String): List<GroupMember> { override fun getMembers(groupPublicKey: String): List<LibSessionGroupMember> =
val groups = configFactory.userGroups ?: return emptyList() configFactory.groupMemberConfig(SessionId.from(groupPublicKey))?.all()?.toList() ?: emptyList()
TODO("Add group specific configs")
}
override fun setServerCapabilities(server: String, capabilities: List<String>) { override fun setServerCapabilities(server: String, capabilities: List<String>) {
return DatabaseComponent.get(context).lokiAPIDatabase().setServerCapabilities(server, capabilities) return DatabaseComponent.get(context).lokiAPIDatabase().setServerCapabilities(server, capabilities)
@ -1603,8 +1615,8 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
} }
getAllContacts().forEach { contact -> getAllContacts().forEach { contact ->
val sessionId = SessionId(contact.sessionID) val sessionId = SessionId(contact.sessionID)
if (sessionId.prefix == IdPrefix.STANDARD && SodiumUtilities.sessionId(sessionId.hexString, blindedId, serverPublicKey)) { if (sessionId.prefix == IdPrefix.STANDARD && SodiumUtilities.sessionId(sessionId.hexString(), blindedId, serverPublicKey)) {
val contactMapping = mapping.copy(sessionId = sessionId.hexString) val contactMapping = mapping.copy(sessionId = sessionId.hexString())
db.addBlindedIdMapping(contactMapping) db.addBlindedIdMapping(contactMapping)
return contactMapping return contactMapping
} }

@ -5,6 +5,9 @@ import android.os.Trace
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.Contacts
import network.loki.messenger.libsession_util.ConversationVolatileConfig import network.loki.messenger.libsession_util.ConversationVolatileConfig
import network.loki.messenger.libsession_util.GroupInfoConfig
import network.loki.messenger.libsession_util.GroupKeysConfig
import network.loki.messenger.libsession_util.GroupMembersConfig
import network.loki.messenger.libsession_util.UserGroupsConfig import network.loki.messenger.libsession_util.UserGroupsConfig
import network.loki.messenger.libsession_util.UserProfile import network.loki.messenger.libsession_util.UserProfile
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
@ -12,7 +15,9 @@ import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsession.utilities.ConfigFactoryUpdateListener import org.session.libsession.utilities.ConfigFactoryUpdateListener
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage
import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.SessionId
import org.thoughtcrime.securesms.database.ConfigDatabase import org.thoughtcrime.securesms.database.ConfigDatabase
import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get
import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.groups.GroupManager
@ -61,7 +66,7 @@ class ConfigFactory(
listeners -= listener listeners -= listener
} }
private inline fun <T> synchronizedWithLog(lock: Any, body: ()->T): T { private inline fun <T> synchronizedWithLog(lock: Any, body: () -> T): T {
Trace.beginSection("synchronizedWithLog") Trace.beginSection("synchronizedWithLog")
val result = synchronized(lock) { val result = synchronized(lock) {
body() body()
@ -72,7 +77,11 @@ class ConfigFactory(
override val user: UserProfile? override val user: UserProfile?
get() = synchronizedWithLog(userLock) { get() = synchronizedWithLog(userLock) {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null if (!ConfigBase.isNewConfigEnabled(
isConfigForcedOn,
SnodeAPI.nowWithOffset
)
) return null
if (_userConfig == null) { if (_userConfig == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return null val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
val userDump = configDatabase.retrieveConfigAndHashes( val userDump = configDatabase.retrieveConfigAndHashes(
@ -92,7 +101,11 @@ class ConfigFactory(
override val contacts: Contacts? override val contacts: Contacts?
get() = synchronizedWithLog(contactsLock) { get() = synchronizedWithLog(contactsLock) {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null if (!ConfigBase.isNewConfigEnabled(
isConfigForcedOn,
SnodeAPI.nowWithOffset
)
) return null
if (_contacts == null) { if (_contacts == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return null val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
val contactsDump = configDatabase.retrieveConfigAndHashes( val contactsDump = configDatabase.retrieveConfigAndHashes(
@ -112,7 +125,11 @@ class ConfigFactory(
override val convoVolatile: ConversationVolatileConfig? override val convoVolatile: ConversationVolatileConfig?
get() = synchronizedWithLog(convoVolatileLock) { get() = synchronizedWithLog(convoVolatileLock) {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null if (!ConfigBase.isNewConfigEnabled(
isConfigForcedOn,
SnodeAPI.nowWithOffset
)
) return null
if (_convoVolatileConfig == null) { if (_convoVolatileConfig == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return null val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
val convoDump = configDatabase.retrieveConfigAndHashes( val convoDump = configDatabase.retrieveConfigAndHashes(
@ -133,7 +150,11 @@ class ConfigFactory(
override val userGroups: UserGroupsConfig? override val userGroups: UserGroupsConfig?
get() = synchronizedWithLog(userGroupsLock) { get() = synchronizedWithLog(userGroupsLock) {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null if (!ConfigBase.isNewConfigEnabled(
isConfigForcedOn,
SnodeAPI.nowWithOffset
)
) return null
if (_userGroups == null) { if (_userGroups == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return null val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
val userGroupsDump = configDatabase.retrieveConfigAndHashes( val userGroupsDump = configDatabase.retrieveConfigAndHashes(
@ -151,6 +172,61 @@ class ConfigFactory(
_userGroups _userGroups
} }
private fun getGroupAuthInfo(groupSessionId: SessionId) = userGroups?.getClosedGroup(groupSessionId.hexString())?.let {
it.adminKey to it.authData
}
override fun groupInfoConfig(groupSessionId: SessionId): GroupInfoConfig? = getGroupAuthInfo(groupSessionId)?.let { (sk, _) ->
// get any potential initial dumps
val dump = configDatabase.retrieveConfigAndHashes(
SharedConfigMessage.Kind.CLOSED_GROUP_INFO.name,
groupSessionId.hexString()
) ?: byteArrayOf()
GroupInfoConfig.newInstance(Hex.fromStringCondensed(groupSessionId.publicKey), sk, dump)
}
override fun groupKeysConfig(groupSessionId: SessionId): GroupKeysConfig? = getGroupAuthInfo(groupSessionId)?.let { (sk, _) ->
// Get the user info or return early
val (userSk, _) = maybeGetUserInfo() ?: return@let null
// Get the group info or return early
val info = groupInfoConfig(groupSessionId) ?: return@let null
// Get the group members or return early
val members = groupMemberConfig(groupSessionId) ?: return@let null
// Get the dump or empty
val dump = configDatabase.retrieveConfigAndHashes(
SharedConfigMessage.Kind.ENCRYPTION_KEYS.name,
groupSessionId.hexString()
) ?: byteArrayOf()
// Put it all together
GroupKeysConfig.newInstance(
userSk,
Hex.fromStringCondensed(groupSessionId.publicKey),
sk,
dump,
info,
members
)
}
override fun groupMemberConfig(groupSessionId: SessionId): GroupMembersConfig? = getGroupAuthInfo(groupSessionId)?.let { (sk, auth) ->
// Get initial dump if we have one
val dump = configDatabase.retrieveConfigAndHashes(
SharedConfigMessage.Kind.CLOSED_GROUP_MEMBERS.name,
groupSessionId.hexString()
) ?: byteArrayOf()
GroupMembersConfig.newInstance(
Hex.fromStringCondensed(groupSessionId.publicKey),
sk,
dump
)
}
override fun getUserConfigs(): List<ConfigBase> = override fun getUserConfigs(): List<ConfigBase> =
listOfNotNull(user, contacts, convoVolatile, userGroups) listOfNotNull(user, contacts, convoVolatile, userGroups)
@ -158,13 +234,23 @@ class ConfigFactory(
private fun persistUserConfigDump(timestamp: Long) = synchronized(userLock) { private fun persistUserConfigDump(timestamp: Long) = 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, timestamp) configDatabase.storeConfig(
SharedConfigMessage.Kind.USER_PROFILE.name,
publicKey,
dumped,
timestamp
)
} }
private fun persistContactsConfigDump(timestamp: Long) = synchronized(contactsLock) { private fun persistContactsConfigDump(timestamp: Long) = 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, timestamp) configDatabase.storeConfig(
SharedConfigMessage.Kind.CONTACTS.name,
publicKey,
dumped,
timestamp
)
} }
private fun persistConvoVolatileConfigDump(timestamp: Long) = synchronized(convoVolatileLock) { private fun persistConvoVolatileConfigDump(timestamp: Long) = synchronized(convoVolatileLock) {
@ -181,7 +267,12 @@ class ConfigFactory(
private fun persistUserGroupsConfigDump(timestamp: Long) = synchronized(userGroupsLock) { private fun persistUserGroupsConfigDump(timestamp: Long) = synchronized(userGroupsLock) {
val dumped = userGroups?.dump() ?: return val dumped = userGroups?.dump() ?: return
val (_, publicKey) = maybeGetUserInfo() ?: return val (_, publicKey) = maybeGetUserInfo() ?: return
configDatabase.storeConfig(SharedConfigMessage.Kind.GROUPS.name, publicKey, dumped, timestamp) configDatabase.storeConfig(
SharedConfigMessage.Kind.GROUPS.name,
publicKey,
dumped,
timestamp
)
} }
override fun persist(forConfigObject: ConfigBase, timestamp: Long) { override fun persist(forConfigObject: ConfigBase, timestamp: Long) {
@ -214,23 +305,21 @@ class ConfigFactory(
if (openGroupId != null) { if (openGroupId != null) {
val userGroups = userGroups ?: return false val userGroups = userGroups ?: return false
val threadId = GroupManager.getOpenGroupThreadID(openGroupId, context) val threadId = GroupManager.getOpenGroupThreadID(openGroupId, context)
val openGroup = get(context).lokiThreadDatabase().getOpenGroupChat(threadId) ?: return false val openGroup =
get(context).lokiThreadDatabase().getOpenGroupChat(threadId) ?: return false
// Not handling the `hidden` behaviour for communities so just indicate the existence // Not handling the `hidden` behaviour for communities so just indicate the existence
return (userGroups.getCommunityInfo(openGroup.server, openGroup.room) != null) return (userGroups.getCommunityInfo(openGroup.server, openGroup.room) != null)
} } else if (groupPublicKey != null) {
else if (groupPublicKey != null) {
val userGroups = userGroups ?: return false val userGroups = userGroups ?: return false
// Not handling the `hidden` behaviour for legacy groups so just indicate the existence // Not handling the `hidden` behaviour for legacy groups so just indicate the existence
return (userGroups.getLegacyGroupInfo(groupPublicKey) != null) return (userGroups.getLegacyGroupInfo(groupPublicKey) != null)
} } else if (publicKey == userPublicKey) {
else if (publicKey == userPublicKey) {
val user = user ?: return false val user = user ?: return false
return (!visibleOnly || user.getNtsPriority() != ConfigBase.PRIORITY_HIDDEN) return (!visibleOnly || user.getNtsPriority() != ConfigBase.PRIORITY_HIDDEN)
} } else if (publicKey != null) {
else if (publicKey != null) {
val contacts = contacts ?: return false val contacts = contacts ?: return false
val targetContact = contacts.get(publicKey) ?: return false val targetContact = contacts.get(publicKey) ?: return false
@ -240,12 +329,18 @@ class ConfigFactory(
return false return false
} }
override fun canPerformChange(variant: String, publicKey: String, changeTimestampMs: Long): Boolean { override fun canPerformChange(
variant: String,
publicKey: String,
changeTimestampMs: Long
): Boolean {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return true if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return true
val lastUpdateTimestampMs = configDatabase.retrieveConfigLastUpdateTimestamp(variant, publicKey) val lastUpdateTimestampMs =
configDatabase.retrieveConfigLastUpdateTimestamp(variant, publicKey)
// Ensure the change occurred after the last config message was handled (minus the buffer period) // Ensure the change occurred after the last config message was handled (minus the buffer period)
return (changeTimestampMs >= (lastUpdateTimestampMs - ConfigFactory.configChangeBufferPeriod)) return (changeTimestampMs >= (lastUpdateTimestampMs - ConfigFactory.configChangeBufferPeriod))
} }
} }

@ -34,16 +34,9 @@ object ClosedGroupManager {
} }
} }
fun ConfigFactory.removeLegacyGroup(group: GroupRecord): Boolean {
val groups = userGroups ?: return false
if (!group.isClosedGroup) return false
val groupPublicKey = GroupUtil.doubleEncodeGroupID(group.getId())
return groups.eraseLegacyGroup(groupPublicKey)
}
fun ConfigFactory.updateLegacyGroup(groupRecipientSettings: Recipient.RecipientSettings, group: GroupRecord) { fun ConfigFactory.updateLegacyGroup(groupRecipientSettings: Recipient.RecipientSettings, group: GroupRecord) {
val groups = userGroups ?: return val groups = userGroups ?: return
if (!group.isClosedGroup) return if (!group.isLegacyClosedGroup) return
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val threadId = storage.getThreadId(group.encodedId) ?: return val threadId = storage.getThreadId(group.encodedId) ?: return
val groupPublicKey = GroupUtil.doubleEncodeGroupID(group.getId()) val groupPublicKey = GroupUtil.doubleEncodeGroupID(group.getId())

@ -7,6 +7,13 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -21,6 +28,7 @@ import nl.komponents.kovenant.ui.successUi
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.sending_receiving.groupSizeLimit import org.session.libsession.messaging.sending_receiving.groupSizeLimit
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Contact
import org.session.libsession.utilities.Device import org.session.libsession.utilities.Device
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
@ -49,8 +57,11 @@ class CreateGroupFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentCreateGroupBinding.inflate(inflater) return ComposeView(requireContext()).apply {
return binding.root setContent {
}
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -121,4 +132,15 @@ class CreateGroupFragment : Fragment() {
context.startActivity(intent) context.startActivity(intent)
} }
}
@Composable
fun MemberList(contacts: List<Contact>, modifier: Modifier = Modifier) {
Avatar
}
@Preview
@Composable
fun ClosedGroupPreview() {
MemberList(contacts = emptyList(), modifier = Modifier.fillMaxWidth())
} }

@ -144,7 +144,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(address)) intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(address))
push(intent) push(intent)
} }
is GlobalSearchAdapter.Model.GroupConversation -> { is GlobalSearchAdapter.Model.LegacyGroupConversation -> {
val groupAddress = Address.fromSerialized(model.groupRecord.encodedId) val groupAddress = Address.fromSerialized(model.groupRecord.encodedId)
val threadId = threadDb.getThreadIdIfExistsFor(Recipient.from(this, groupAddress, false)) val threadId = threadDb.getThreadIdIfExistsFor(Recipient.from(this, groupAddress, false))
if (threadId >= 0) { if (threadId >= 0) {
@ -257,7 +257,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
globalSearchViewModel.result.collect { result -> globalSearchViewModel.result.collect { result ->
val currentUserPublicKey = publicKey val currentUserPublicKey = publicKey
val contactAndGroupList = result.contacts.map { GlobalSearchAdapter.Model.Contact(it) } + val contactAndGroupList = result.contacts.map { GlobalSearchAdapter.Model.Contact(it) } +
result.threads.map { GlobalSearchAdapter.Model.GroupConversation(it) } result.threads.map { GlobalSearchAdapter.Model.LegacyGroupConversation(it) }
val contactResults = contactAndGroupList.toMutableList() val contactResults = contactAndGroupList.toMutableList()
@ -642,14 +642,18 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
// Cancel any outstanding jobs // Cancel any outstanding jobs
DatabaseComponent.get(context).sessionJobDatabase().cancelPendingMessageSendJobs(threadID) DatabaseComponent.get(context).sessionJobDatabase().cancelPendingMessageSendJobs(threadID)
// Send a leave group message if this is an active closed group // Send a leave group message if this is an active closed group
if (recipient.address.isClosedGroup && DatabaseComponent.get(context).groupDatabase().isActive(recipient.address.toGroupString())) { if (recipient.address.isLegacyClosedGroup && DatabaseComponent.get(context).groupDatabase().isActive(recipient.address.toGroupString())) {
try { try {
GroupUtil.doubleDecodeGroupID(recipient.address.toString()).toHexString() GroupUtil.doubleDecodeGroupID(recipient.address.toString()).toHexString()
.takeIf(DatabaseComponent.get(context).lokiAPIDatabase()::isClosedGroup) .takeIf(DatabaseComponent.get(context).lokiAPIDatabase()::isClosedGroup)
?.let { MessageSender.explicitLeave(it, false) } ?.let { MessageSender.explicitLeave(it, false) }
} catch (_: IOException) { } catch (e: IOException) {
Log.e("Loki", e)
} }
} }
if (recipient.address.isClosedGroup) {
TODO("Implement leaving / deleting a new closed group conversation")
}
// Delete the conversation // Delete the conversation
val v2OpenGroup = DatabaseComponent.get(context).lokiThreadDatabase().getOpenGroupChat(threadID) val v2OpenGroup = DatabaseComponent.get(context).lokiThreadDatabase().getOpenGroupChat(threadID)
if (v2OpenGroup != null) { if (v2OpenGroup != null) {

@ -9,9 +9,10 @@ import androidx.recyclerview.widget.RecyclerView
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ViewGlobalSearchHeaderBinding import network.loki.messenger.databinding.ViewGlobalSearchHeaderBinding
import network.loki.messenger.databinding.ViewGlobalSearchResultBinding import network.loki.messenger.databinding.ViewGlobalSearchResultBinding
import network.loki.messenger.libsession_util.util.GroupInfo
import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.GroupRecord
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.mms.GlideApp import org.session.libsignal.utilities.SessionId
import org.thoughtcrime.securesms.search.model.MessageResult import org.thoughtcrime.securesms.search.model.MessageResult
import java.security.InvalidParameterException import java.security.InvalidParameterException
import org.session.libsession.messaging.contacts.Contact as ContactModel import org.session.libsession.messaging.contacts.Contact as ContactModel
@ -98,7 +99,7 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi
fun bind(query: String, model: Model) { fun bind(query: String, model: Model) {
binding.searchResultProfilePicture.recycle() binding.searchResultProfilePicture.recycle()
when (model) { when (model) {
is Model.GroupConversation -> bindModel(query, model) is Model.LegacyGroupConversation -> bindModel(query, model)
is Model.Contact -> bindModel(query, model) is Model.Contact -> bindModel(query, model)
is Model.Message -> bindModel(query, model) is Model.Message -> bindModel(query, model)
is Model.SavedMessages -> bindModel(model) is Model.SavedMessages -> bindModel(model)
@ -119,7 +120,8 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi
data class Header(@StringRes val title: Int) : Model() data class Header(@StringRes val title: Int) : Model()
data class SavedMessages(val currentUserPublicKey: String): Model() data class SavedMessages(val currentUserPublicKey: String): Model()
data class Contact(val contact: ContactModel) : Model() data class Contact(val contact: ContactModel) : Model()
data class GroupConversation(val groupRecord: GroupRecord) : Model() data class LegacyGroupConversation(val groupRecord: GroupRecord) : Model()
data class ClosedGroupConversation(val sessionId: SessionId)
data class Message(val messageResult: MessageResult, val unread: Int) : Model() data class Message(val messageResult: MessageResult, val unread: Int) : Model()
} }

@ -11,7 +11,7 @@ import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.ContentView import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.ContentView
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.GroupConversation import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.LegacyGroupConversation
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Header import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Header
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Message import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Message
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.SavedMessages import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.SavedMessages
@ -65,7 +65,7 @@ fun ContentView.bindQuery(query: String, model: GlobalSearchAdapter.Model) {
binding.searchResultSubtitle.isVisible = true binding.searchResultSubtitle.isVisible = true
binding.searchResultTitle.text = model.messageResult.conversationRecipient.toShortString() binding.searchResultTitle.text = model.messageResult.conversationRecipient.toShortString()
} }
is GroupConversation -> { is LegacyGroupConversation -> {
binding.searchResultTitle.text = getHighlight( binding.searchResultTitle.text = getHighlight(
query, query,
model.groupRecord.title model.groupRecord.title
@ -86,10 +86,10 @@ private fun getHighlight(query: String?, toSearch: String): Spannable? {
return SearchUtil.getHighlightedSpan(Locale.getDefault(), BoldStyleFactory, toSearch, query) return SearchUtil.getHighlightedSpan(Locale.getDefault(), BoldStyleFactory, toSearch, query)
} }
fun ContentView.bindModel(query: String?, model: GroupConversation) { fun ContentView.bindModel(query: String?, model: LegacyGroupConversation) {
binding.searchResultProfilePicture.isVisible = true binding.searchResultProfilePicture.isVisible = true
binding.searchResultSavedMessages.isVisible = false binding.searchResultSavedMessages.isVisible = false
binding.searchResultSubtitle.isVisible = model.groupRecord.isClosedGroup binding.searchResultSubtitle.isVisible = model.groupRecord.isLegacyClosedGroup
binding.searchResultTimestamp.isVisible = false binding.searchResultTimestamp.isVisible = false
val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupRecord.encodedId), false) val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupRecord.encodedId), false)
binding.searchResultProfilePicture.update(threadRecipient) binding.searchResultProfilePicture.update(threadRecipient)
@ -102,7 +102,7 @@ fun ContentView.bindModel(query: String?, model: GroupConversation) {
val address = it.address.serialize() val address = it.address.serialize()
it.name ?: "${address.take(4)}...${address.takeLast(4)}" it.name ?: "${address.take(4)}...${address.takeLast(4)}"
} }
if (model.groupRecord.isClosedGroup) { if (model.groupRecord.isLegacyClosedGroup) {
binding.searchResultSubtitle.text = getHighlight(query, membersString) binding.searchResultSubtitle.text = getHighlight(query, membersString)
} }
} }
@ -132,11 +132,6 @@ fun ContentView.bindModel(query: String?, model: Message) {
binding.searchResultProfilePicture.isVisible = true binding.searchResultProfilePicture.isVisible = true
binding.searchResultSavedMessages.isVisible = false binding.searchResultSavedMessages.isVisible = false
binding.searchResultTimestamp.isVisible = true binding.searchResultTimestamp.isVisible = true
// val hasUnreads = model.unread > 0
// binding.unreadCountIndicator.isVisible = hasUnreads
// if (hasUnreads) {
// binding.unreadCountTextView.text = model.unread.toString()
// }
binding.searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(binding.root.context, Locale.getDefault(), model.messageResult.sentTimestampMs) binding.searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(binding.root.context, Locale.getDefault(), model.messageResult.sentTimestampMs)
binding.searchResultProfilePicture.update(model.messageResult.conversationRecipient) binding.searchResultProfilePicture.update(model.messageResult.conversationRecipient)
val textSpannable = SpannableStringBuilder() val textSpannable = SpannableStringBuilder()

@ -42,7 +42,6 @@ import com.goterl.lazysodium.utils.KeyPair;
import org.session.libsession.messaging.open_groups.OpenGroup; import org.session.libsession.messaging.open_groups.OpenGroup;
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
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.snode.SnodeAPI; import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Address;
@ -52,6 +51,7 @@ import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsignal.utilities.IdPrefix; import org.session.libsignal.utilities.IdPrefix;
import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.Log;
import org.session.libsignal.utilities.SessionId;
import org.session.libsignal.utilities.Util; import org.session.libsignal.utilities.Util;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.contacts.ContactUtil; import org.thoughtcrime.securesms.contacts.ContactUtil;
@ -555,7 +555,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
if (openGroup != null && edKeyPair != null) { if (openGroup != null && edKeyPair != null) {
KeyPair blindedKeyPair = SodiumUtilities.blindedKeyPair(openGroup.getPublicKey(), edKeyPair); KeyPair blindedKeyPair = SodiumUtilities.blindedKeyPair(openGroup.getPublicKey(), edKeyPair);
if (blindedKeyPair != null) { if (blindedKeyPair != null) {
return new SessionId(IdPrefix.BLINDED, blindedKeyPair.getPublicKey().getAsBytes()).getHexString(); return new SessionId(IdPrefix.BLINDED, blindedKeyPair.getPublicKey().getAsBytes()).hexString();
} }
} }
return null; return null;

@ -9,11 +9,10 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.session.libsession.messaging.utilities.SessionId; import org.session.libsignal.utilities.SessionId;
import org.thoughtcrime.securesms.components.ProfilePictureView; import org.thoughtcrime.securesms.components.ProfilePictureView;
import org.thoughtcrime.securesms.components.emoji.EmojiImageView; import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
import org.thoughtcrime.securesms.database.model.MessageId; import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.mms.GlideApp;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;

@ -5,11 +5,11 @@ import network.loki.messenger.libsession_util.util.UserPic
import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob
import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.SSKEnvironment
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.SessionId
import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.util package org.thoughtcrime.securesms.util
import android.content.Context import android.content.Context
import android.provider.Telephony.Sms.Conversations
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.Contacts
import network.loki.messenger.libsession_util.ConversationVolatileConfig import network.loki.messenger.libsession_util.ConversationVolatileConfig
@ -26,6 +27,7 @@ import org.session.libsession.utilities.WindowDebouncer
import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.crypto.ecc.DjbECPublicKey
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.SessionId
import org.session.libsignal.utilities.toHexString import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.ThreadDatabase
@ -199,6 +201,11 @@ object ConfigurationMessageUtilities {
convoConfig.getOrConstructCommunity(base, room, pubKey) convoConfig.getOrConstructCommunity(base, room, pubKey)
} }
recipient.isClosedGroupRecipient -> { recipient.isClosedGroupRecipient -> {
// It's probably safe to assume there will never be a case where new closed groups will ever be there before a dump is created...
// but just in case...
convoConfig.getOrConstructClosedGroup(recipient.address.serialize())
}
recipient.isLegacyClosedGroupRecipient -> {
val groupPublicKey = GroupUtil.doubleDecodeGroupId(recipient.address.serialize()) val groupPublicKey = GroupUtil.doubleDecodeGroupId(recipient.address.serialize())
convoConfig.getOrConstructLegacyGroup(groupPublicKey) convoConfig.getOrConstructLegacyGroup(groupPublicKey)
} }
@ -241,7 +248,7 @@ object ConfigurationMessageUtilities {
} }
val allLgc = storage.getAllGroups(includeInactive = false).filter { val allLgc = storage.getAllGroups(includeInactive = false).filter {
it.isClosedGroup && it.isActive && it.members.size > 1 it.isLegacyClosedGroup && it.isActive && it.members.size > 1
}.mapNotNull { group -> }.mapNotNull { group ->
val groupAddress = Address.fromSerialized(group.encodedId) val groupAddress = Address.fromSerialized(group.encodedId)
val groupPublicKey = GroupUtil.doubleDecodeGroupID(groupAddress.serialize()).toHexString() val groupPublicKey = GroupUtil.doubleDecodeGroupID(groupAddress.serialize()).toHexString()
@ -252,7 +259,7 @@ object ConfigurationMessageUtilities {
val admins = group.admins.map { it.serialize() to true }.toMap() val admins = group.admins.map { it.serialize() to true }.toMap()
val members = group.members.filterNot { it.serialize() !in admins.keys }.map { it.serialize() to false }.toMap() val members = group.members.filterNot { it.serialize() !in admins.keys }.map { it.serialize() to false }.toMap()
GroupInfo.LegacyGroupInfo( GroupInfo.LegacyGroupInfo(
sessionId = groupPublicKey, sessionId = SessionId.from(groupPublicKey),
name = group.title, name = group.title,
members = admins + members, members = admins + members,
priority = if (isPinned) ConfigBase.PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE, priority = if (isPinned) ConfigBase.PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE,

@ -13,6 +13,8 @@ fun ConversationVolatileConfig.getConversationUnread(thread: ThreadRecord): Bool
&& recipient.address.serialize().startsWith(IdPrefix.STANDARD.value)) { && recipient.address.serialize().startsWith(IdPrefix.STANDARD.value)) {
return getOneToOne(recipient.address.serialize())?.unread == true return getOneToOne(recipient.address.serialize())?.unread == true
} else if (recipient.isClosedGroupRecipient) { } else if (recipient.isClosedGroupRecipient) {
return getClosedGroup(recipient.address.serialize())?.unread == true
} else if (recipient.isLegacyClosedGroupRecipient) {
return getLegacyClosedGroup(GroupUtil.doubleDecodeGroupId(recipient.address.toGroupString()))?.unread == true return getLegacyClosedGroup(GroupUtil.doubleDecodeGroupId(recipient.address.toGroupString()))?.unread == true
} else if (recipient.isOpenGroupRecipient) { } else if (recipient.isOpenGroupRecipient) {
val openGroup = MessagingModuleConfiguration.shared.storage.getOpenGroup(thread.threadId) ?: return false val openGroup = MessagingModuleConfiguration.shared.storage.getOpenGroup(thread.threadId) ?: return false

@ -601,7 +601,7 @@ class InstrumentedTests {
assertEquals("New Group", infoConf.getName()) assertEquals("New Group", infoConf.getName())
infoConf.setCreated(System.currentTimeMillis()) infoConf.setCreated(System.currentTimeMillis())
assertThat(infoConf.getCreated(), notNullValue()) assertThat(infoConf.getCreated(), notNullValue())
val memberConf = GroupMemberConfig.newInstance(groupPublic, groupSecret) val memberConf = GroupMembersConfig.newInstance(groupPublic, groupSecret)
memberConf.set( memberConf.set(
GroupMember( GroupMember(
sessionId = "05"+Hex.toStringCondensed(userCurve), sessionId = "05"+Hex.toStringCondensed(userCurve),
@ -637,7 +637,7 @@ class InstrumentedTests {
assertEquals("New Group", infoConf.getName()) assertEquals("New Group", infoConf.getName())
infoConf.setCreated(System.currentTimeMillis()) infoConf.setCreated(System.currentTimeMillis())
assertThat(infoConf.getCreated(), notNullValue()) assertThat(infoConf.getCreated(), notNullValue())
val memberConf = GroupMemberConfig.newInstance(groupPublic, groupSecret) val memberConf = GroupMembersConfig.newInstance(groupPublic, groupSecret)
memberConf.set( memberConf.set(
GroupMember( GroupMember(
sessionId = "05"+Hex.toStringCondensed(userCurve), sessionId = "05"+Hex.toStringCondensed(userCurve),

@ -177,3 +177,11 @@ Java_network_loki_messenger_libsession_1util_GroupInfoConfig_storageNamespace(JN
auto group_info = ptrToInfo(env, thiz); auto group_info = ptrToInfo(env, thiz);
return static_cast<jlong>(group_info->storage_namespace()); return static_cast<jlong>(group_info->storage_namespace());
} }
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_GroupInfoConfig_id(JNIEnv *env, jobject thiz) {
std::lock_guard guard{util::util_mutex_};
auto group_info = ptrToInfo(env, thiz);
return util::serialize_session_id(env, group_info->id);
}

@ -65,6 +65,7 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_groupKeys(JNIEnv *e
extern "C" extern "C"
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_GroupKeysConfig_loadKey(JNIEnv *env, jobject thiz, Java_network_loki_messenger_libsession_1util_GroupKeysConfig_loadKey(JNIEnv *env, jobject thiz,
jstring hash,
jbyteArray data, jbyteArray data,
jbyteArray msg_id, jbyteArray msg_id,
jlong timestamp_ms, jlong timestamp_ms,
@ -72,11 +73,13 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_loadKey(JNIEnv *env
jobject members_jobject) { jobject members_jobject) {
std::lock_guard lock{util::util_mutex_}; std::lock_guard lock{util::util_mutex_};
auto keys = ptrToKeys(env, thiz); auto keys = ptrToKeys(env, thiz);
auto hash_bytes = env->GetStringUTFChars(hash, nullptr);
auto data_bytes = util::ustring_from_bytes(env, data); auto data_bytes = util::ustring_from_bytes(env, data);
auto msg_bytes = util::ustring_from_bytes(env, msg_id); auto msg_bytes = util::ustring_from_bytes(env, msg_id);
auto info = ptrToInfo(env, info_jobject); auto info = ptrToInfo(env, info_jobject);
auto members = ptrToMembers(env, members_jobject); auto members = ptrToMembers(env, members_jobject);
keys->load_key_message(data_bytes, timestamp_ms, *info, *members); keys->load_key_message(hash_bytes, data_bytes, timestamp_ms, *info, *members);
env->ReleaseStringUTFChars(hash, hash_bytes);
} }
extern "C" extern "C"

@ -2,7 +2,7 @@
extern "C" extern "C"
JNIEXPORT jobject JNICALL JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_GroupMemberConfig_00024Companion_newInstance( Java_network_loki_messenger_libsession_1util_GroupMembersConfig_00024Companion_newInstance(
JNIEnv *env, jobject thiz, jbyteArray pub_key, jbyteArray secret_key, JNIEnv *env, jobject thiz, jbyteArray pub_key, jbyteArray secret_key,
jbyteArray initial_dump) { jbyteArray initial_dump) {
std::lock_guard lock{util::util_mutex_}; std::lock_guard lock{util::util_mutex_};
@ -20,7 +20,7 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_00024Companion_ne
auto* group_members = new session::config::groups::Members(pub_key_bytes, secret_key_optional, initial_dump_optional); auto* group_members = new session::config::groups::Members(pub_key_bytes, secret_key_optional, initial_dump_optional);
jclass groupMemberClass = env->FindClass("network/loki/messenger/libsession_util/GroupMemberConfig"); jclass groupMemberClass = env->FindClass("network/loki/messenger/libsession_util/GroupMembersConfig");
jmethodID constructor = env->GetMethodID(groupMemberClass, "<init>", "(J)V"); jmethodID constructor = env->GetMethodID(groupMemberClass, "<init>", "(J)V");
jobject newConfig = env->NewObject(groupMemberClass, constructor, reinterpret_cast<jlong>(group_members)); jobject newConfig = env->NewObject(groupMemberClass, constructor, reinterpret_cast<jlong>(group_members));
@ -29,7 +29,7 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_00024Companion_ne
extern "C" extern "C"
JNIEXPORT jobject JNICALL JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_GroupMemberConfig_all(JNIEnv *env, jobject thiz) { Java_network_loki_messenger_libsession_1util_GroupMembersConfig_all(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_}; std::lock_guard lock{util::util_mutex_};
auto config = ptrToMembers(env, thiz); auto config = ptrToMembers(env, thiz);
jclass stack = env->FindClass("java/util/Stack"); jclass stack = env->FindClass("java/util/Stack");
@ -45,8 +45,8 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_all(JNIEnv *env,
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_GroupMemberConfig_erase(JNIEnv *env, jobject thiz, Java_network_loki_messenger_libsession_1util_GroupMembersConfig_erase(JNIEnv *env, jobject thiz,
jobject group_member) { jobject group_member) {
auto config = ptrToMembers(env, thiz); auto config = ptrToMembers(env, thiz);
auto member = util::deserialize_group_member(env, group_member); auto member = util::deserialize_group_member(env, group_member);
return config->erase(member.session_id); return config->erase(member.session_id);
@ -54,8 +54,8 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_erase(JNIEnv *env
extern "C" extern "C"
JNIEXPORT jobject JNICALL JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_GroupMemberConfig_get(JNIEnv *env, jobject thiz, Java_network_loki_messenger_libsession_1util_GroupMembersConfig_get(JNIEnv *env, jobject thiz,
jstring pub_key_hex) { jstring pub_key_hex) {
std::lock_guard lock{util::util_mutex_}; std::lock_guard lock{util::util_mutex_};
auto config = ptrToMembers(env, thiz); auto config = ptrToMembers(env, thiz);
auto pub_key_bytes = env->GetStringUTFChars(pub_key_hex, nullptr); auto pub_key_bytes = env->GetStringUTFChars(pub_key_hex, nullptr);
@ -70,9 +70,9 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_get(JNIEnv *env,
extern "C" extern "C"
JNIEXPORT jobject JNICALL JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_GroupMemberConfig_getOrConstruct(JNIEnv *env, Java_network_loki_messenger_libsession_1util_GroupMembersConfig_getOrConstruct(JNIEnv *env,
jobject thiz, jobject thiz,
jstring pub_key_hex) { jstring pub_key_hex) {
std::lock_guard lock{util::util_mutex_}; std::lock_guard lock{util::util_mutex_};
auto config = ptrToMembers(env, thiz); auto config = ptrToMembers(env, thiz);
auto pub_key_bytes = env->GetStringUTFChars(pub_key_hex, nullptr); auto pub_key_bytes = env->GetStringUTFChars(pub_key_hex, nullptr);
@ -84,8 +84,8 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_getOrConstruct(JN
extern "C" extern "C"
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_GroupMemberConfig_set(JNIEnv *env, jobject thiz, Java_network_loki_messenger_libsession_1util_GroupMembersConfig_set(JNIEnv *env, jobject thiz,
jobject group_member) { jobject group_member) {
std::lock_guard lock{util::util_mutex_}; std::lock_guard lock{util::util_mutex_};
auto config = ptrToMembers(env, thiz); auto config = ptrToMembers(env, thiz);
auto deserialized = util::deserialize_group_member(env, group_member); auto deserialized = util::deserialize_group_member(env, group_member);

@ -10,6 +10,8 @@ import network.loki.messenger.libsession_util.util.UserPic
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.SessionId
import java.io.Closeable
import java.util.Stack import java.util.Stack
@ -27,7 +29,7 @@ sealed class ConfigBase(protected val /* yucky */ pointer: Long) {
is UserGroupsConfig -> Kind.GROUPS is UserGroupsConfig -> Kind.GROUPS
is GroupInfoConfig -> Kind.CLOSED_GROUP_INFO is GroupInfoConfig -> Kind.CLOSED_GROUP_INFO
is GroupKeysConfig -> Kind.ENCRYPTION_KEYS is GroupKeysConfig -> Kind.ENCRYPTION_KEYS
is GroupMemberConfig -> Kind.CLOSED_GROUP_MEMBERS is GroupMembersConfig -> Kind.CLOSED_GROUP_MEMBERS
} }
// TODO: time in future to activate (hardcoded to 1st jan 2024 for testing, change before release) // TODO: time in future to activate (hardcoded to 1st jan 2024 for testing, change before release)
@ -217,7 +219,7 @@ class UserGroupsConfig(pointer: Long): ConfigBase(pointer) {
external fun createGroup(): GroupInfo.ClosedGroupInfo external fun createGroup(): GroupInfo.ClosedGroupInfo
} }
class GroupInfoConfig(pointer: Long): ConfigBase(pointer) { class GroupInfoConfig(pointer: Long): ConfigBase(pointer), Closeable {
companion object { companion object {
init { init {
System.loadLibrary("session_util") System.loadLibrary("session_util")
@ -229,6 +231,7 @@ class GroupInfoConfig(pointer: Long): ConfigBase(pointer) {
initialDump: ByteArray = byteArrayOf() initialDump: ByteArray = byteArrayOf()
): GroupInfoConfig ): GroupInfoConfig
} }
external fun id(): SessionId
external fun destroyGroup() external fun destroyGroup()
external fun getCreated(): Long? external fun getCreated(): Long?
external fun getDeleteAttachmentsBefore(): Long? external fun getDeleteAttachmentsBefore(): Long?
@ -244,9 +247,12 @@ class GroupInfoConfig(pointer: Long): ConfigBase(pointer) {
external fun setName(newName: String) external fun setName(newName: String)
external fun setProfilePic(newProfilePic: UserPic) external fun setProfilePic(newProfilePic: UserPic)
external fun storageNamespace(): Long external fun storageNamespace(): Long
override fun close() {
free()
}
} }
class GroupMemberConfig(pointer: Long): ConfigBase(pointer) { class GroupMembersConfig(pointer: Long): ConfigBase(pointer), Closeable {
companion object { companion object {
init { init {
System.loadLibrary("session_util") System.loadLibrary("session_util")
@ -255,16 +261,19 @@ class GroupMemberConfig(pointer: Long): ConfigBase(pointer) {
pubKey: ByteArray, pubKey: ByteArray,
secretKey: ByteArray = byteArrayOf(), secretKey: ByteArray = byteArrayOf(),
initialDump: ByteArray = byteArrayOf() initialDump: ByteArray = byteArrayOf()
): GroupMemberConfig ): GroupMembersConfig
} }
external fun all(): Stack<GroupMember> external fun all(): Stack<GroupMember>
external fun erase(groupMember: GroupMember): Boolean external fun erase(groupMember: GroupMember): Boolean
external fun get(pubKeyHex: String): GroupMember? external fun get(pubKeyHex: String): GroupMember?
external fun getOrConstruct(pubKeyHex: String): GroupMember external fun getOrConstruct(pubKeyHex: String): GroupMember
external fun set(groupMember: GroupMember) external fun set(groupMember: GroupMember)
override fun close() {
free()
}
} }
class GroupKeysConfig(pointer: Long): ConfigBase(pointer) { class GroupKeysConfig(pointer: Long): ConfigBase(pointer), Closeable {
companion object { companion object {
init { init {
System.loadLibrary("session_util") System.loadLibrary("session_util")
@ -275,17 +284,21 @@ class GroupKeysConfig(pointer: Long): ConfigBase(pointer) {
groupSecretKey: ByteArray = byteArrayOf(), groupSecretKey: ByteArray = byteArrayOf(),
initialDump: ByteArray = byteArrayOf(), initialDump: ByteArray = byteArrayOf(),
info: GroupInfoConfig, info: GroupInfoConfig,
members: GroupMemberConfig members: GroupMembersConfig
): GroupKeysConfig ): GroupKeysConfig
} }
external fun groupKeys(): Stack<ByteArray> external fun groupKeys(): Stack<ByteArray>
external fun loadKey(data: ByteArray, external fun loadKey(hash: String,
data: ByteArray,
msgId: ByteArray, msgId: ByteArray,
timestampMs: Long, timestampMs: Long,
info: GroupInfoConfig, info: GroupInfoConfig,
members: GroupMemberConfig) members: GroupMembersConfig)
external fun needsRekey(): Boolean external fun needsRekey(): Boolean
external fun pendingKey(): ByteArray? external fun pendingKey(): ByteArray?
external fun pendingPush(): ByteArray? external fun pendingPush(): ByteArray?
external fun rekey(info: GroupInfoConfig, members: GroupMemberConfig): ByteArray external fun rekey(info: GroupInfoConfig, members: GroupMembersConfig): ByteArray
override fun close() {
free()
}
} }

@ -154,7 +154,7 @@ interface StorageProtocol {
fun setExpirationTimer(address: String, duration: Int) fun setExpirationTimer(address: String, duration: Int)
// Closed Groups // Closed Groups
fun getMembers(groupPublicKey: String): List<GroupMember> fun getMembers(groupPublicKey: String): List<network.loki.messenger.libsession_util.util.GroupMember>
// Groups // Groups
fun getAllGroups(includeInactive: Boolean): List<GroupRecord> fun getAllGroups(includeInactive: Boolean): List<GroupRecord>

@ -23,6 +23,8 @@ class Address private constructor(address: String) : Parcelable, Comparable<Addr
get() = GroupUtil.isEncodedGroup(address) || address.startsWith(IdPrefix.GROUP.value) get() = GroupUtil.isEncodedGroup(address) || address.startsWith(IdPrefix.GROUP.value)
val isLegacyClosedGroup: Boolean val isLegacyClosedGroup: Boolean
get() = GroupUtil.isLegacyClosedGroup(address) get() = GroupUtil.isLegacyClosedGroup(address)
val isClosedGroup: Boolean
get() = address.startsWith(IdPrefix.GROUP.value)
val isOpenGroup: Boolean val isOpenGroup: Boolean
get() = GroupUtil.isOpenGroup(address) get() = GroupUtil.isOpenGroup(address)
val isOpenGroupInbox: Boolean val isOpenGroupInbox: Boolean

@ -3,14 +3,23 @@ package org.session.libsession.utilities
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.Contacts
import network.loki.messenger.libsession_util.ConversationVolatileConfig import network.loki.messenger.libsession_util.ConversationVolatileConfig
import network.loki.messenger.libsession_util.GroupInfoConfig
import network.loki.messenger.libsession_util.GroupKeysConfig
import network.loki.messenger.libsession_util.GroupMembersConfig
import network.loki.messenger.libsession_util.UserGroupsConfig import network.loki.messenger.libsession_util.UserGroupsConfig
import network.loki.messenger.libsession_util.UserProfile import network.loki.messenger.libsession_util.UserProfile
import org.session.libsignal.utilities.SessionId
interface ConfigFactoryProtocol { interface ConfigFactoryProtocol {
val user: UserProfile? val user: UserProfile?
val contacts: Contacts? val contacts: Contacts?
val convoVolatile: ConversationVolatileConfig? val convoVolatile: ConversationVolatileConfig?
val userGroups: UserGroupsConfig? val userGroups: UserGroupsConfig?
fun groupInfoConfig(groupSessionId: SessionId): GroupInfoConfig?
fun groupKeysConfig(groupSessionId: SessionId): GroupKeysConfig?
fun groupMemberConfig(groupSessionId: SessionId): GroupMembersConfig?
fun getUserConfigs(): List<ConfigBase> fun getUserConfigs(): List<ConfigBase>
fun persist(forConfigObject: ConfigBase, timestamp: Long) fun persist(forConfigObject: ConfigBase, timestamp: Long)

@ -476,6 +476,10 @@ public class Recipient implements RecipientModifiedListener {
return address.isLegacyClosedGroup(); return address.isLegacyClosedGroup();
} }
public boolean isClosedGroupRecipient() {
return address.isClosedGroup();
}
@Deprecated @Deprecated
public boolean isPushGroupRecipient() { public boolean isPushGroupRecipient() {

Loading…
Cancel
Save