diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionManagerUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionManagerUtilities.kt index 4c6435b85e..b2803b2c5c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionManagerUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionManagerUtilities.kt @@ -19,7 +19,7 @@ object MentionManagerUtilities { } recipient.address.isClosedGroup -> { 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 -> { val messageDatabase = DatabaseComponent.get(context).mmsSmsDatabase() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index ff5577a624..a11916ea23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -10,7 +10,7 @@ import network.loki.messenger.libsession_util.Contacts 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.GroupMemberConfig +import network.loki.messenger.libsession_util.GroupMembersConfig import network.loki.messenger.libsession_util.UserGroupsConfig import network.loki.messenger.libsession_util.UserProfile 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 java.security.MessageDigest 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, ThreadDatabase.ConversationThreadUpdateListener { @@ -460,9 +461,9 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co is Contacts -> updateContacts(forConfigObject) is ConversationVolatileConfig -> updateConvoVolatile(forConfigObject) is UserGroupsConfig -> updateUserGroups(forConfigObject) - is GroupInfoConfig -> TODO() - is GroupKeysConfig -> TODO() - is GroupMemberConfig -> TODO() + is GroupInfoConfig -> updateGroupInfo(forConfigObject) + is GroupKeysConfig -> updateGroupKeys(forConfigObject) + 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) { val extracted = contacts.all().toList() addLibSessionContacts(extracted) @@ -1030,10 +1044,8 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co } } - override fun getMembers(groupPublicKey: String): List { - val groups = configFactory.userGroups ?: return emptyList() - TODO("Add group specific configs") - } + override fun getMembers(groupPublicKey: String): List = + configFactory.groupMemberConfig(SessionId.from(groupPublicKey))?.all()?.toList() ?: emptyList() override fun setServerCapabilities(server: String, capabilities: List) { 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 -> val sessionId = SessionId(contact.sessionID) - if (sessionId.prefix == IdPrefix.STANDARD && SodiumUtilities.sessionId(sessionId.hexString, blindedId, serverPublicKey)) { - val contactMapping = mapping.copy(sessionId = sessionId.hexString) + if (sessionId.prefix == IdPrefix.STANDARD && SodiumUtilities.sessionId(sessionId.hexString(), blindedId, serverPublicKey)) { + val contactMapping = mapping.copy(sessionId = sessionId.hexString()) db.addBlindedIdMapping(contactMapping) return contactMapping } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt index d664ffedb2..2c44b3d4e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt @@ -5,6 +5,9 @@ import android.os.Trace 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.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.UserProfile 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.TextSecurePreferences import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage +import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.SessionId import org.thoughtcrime.securesms.database.ConfigDatabase import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import org.thoughtcrime.securesms.groups.GroupManager @@ -61,7 +66,7 @@ class ConfigFactory( listeners -= listener } - private inline fun synchronizedWithLog(lock: Any, body: ()->T): T { + private inline fun synchronizedWithLog(lock: Any, body: () -> T): T { Trace.beginSection("synchronizedWithLog") val result = synchronized(lock) { body() @@ -72,7 +77,11 @@ class ConfigFactory( override val user: UserProfile? get() = synchronizedWithLog(userLock) { - if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null + if (!ConfigBase.isNewConfigEnabled( + isConfigForcedOn, + SnodeAPI.nowWithOffset + ) + ) return null if (_userConfig == null) { val (secretKey, publicKey) = maybeGetUserInfo() ?: return null val userDump = configDatabase.retrieveConfigAndHashes( @@ -92,7 +101,11 @@ class ConfigFactory( override val contacts: Contacts? get() = synchronizedWithLog(contactsLock) { - if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null + if (!ConfigBase.isNewConfigEnabled( + isConfigForcedOn, + SnodeAPI.nowWithOffset + ) + ) return null if (_contacts == null) { val (secretKey, publicKey) = maybeGetUserInfo() ?: return null val contactsDump = configDatabase.retrieveConfigAndHashes( @@ -112,7 +125,11 @@ class ConfigFactory( override val convoVolatile: ConversationVolatileConfig? get() = synchronizedWithLog(convoVolatileLock) { - if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null + if (!ConfigBase.isNewConfigEnabled( + isConfigForcedOn, + SnodeAPI.nowWithOffset + ) + ) return null if (_convoVolatileConfig == null) { val (secretKey, publicKey) = maybeGetUserInfo() ?: return null val convoDump = configDatabase.retrieveConfigAndHashes( @@ -133,7 +150,11 @@ class ConfigFactory( override val userGroups: UserGroupsConfig? get() = synchronizedWithLog(userGroupsLock) { - if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null + if (!ConfigBase.isNewConfigEnabled( + isConfigForcedOn, + SnodeAPI.nowWithOffset + ) + ) return null if (_userGroups == null) { val (secretKey, publicKey) = maybeGetUserInfo() ?: return null val userGroupsDump = configDatabase.retrieveConfigAndHashes( @@ -151,6 +172,61 @@ class ConfigFactory( _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 = listOfNotNull(user, contacts, convoVolatile, userGroups) @@ -158,13 +234,23 @@ class ConfigFactory( private fun persistUserConfigDump(timestamp: Long) = synchronized(userLock) { val dumped = user?.dump() ?: 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) { val dumped = contacts?.dump() ?: 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) { @@ -181,7 +267,12 @@ class ConfigFactory( private fun persistUserGroupsConfigDump(timestamp: Long) = synchronized(userGroupsLock) { val dumped = userGroups?.dump() ?: 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) { @@ -214,23 +305,21 @@ class ConfigFactory( if (openGroupId != null) { val userGroups = userGroups ?: return false 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 return (userGroups.getCommunityInfo(openGroup.server, openGroup.room) != null) - } - else if (groupPublicKey != null) { + } else if (groupPublicKey != null) { val userGroups = userGroups ?: return false // Not handling the `hidden` behaviour for legacy groups so just indicate the existence return (userGroups.getLegacyGroupInfo(groupPublicKey) != null) - } - else if (publicKey == userPublicKey) { + } else if (publicKey == userPublicKey) { val user = user ?: return false return (!visibleOnly || user.getNtsPriority() != ConfigBase.PRIORITY_HIDDEN) - } - else if (publicKey != null) { + } else if (publicKey != null) { val contacts = contacts ?: return false val targetContact = contacts.get(publicKey) ?: return false @@ -240,12 +329,18 @@ class ConfigFactory( 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 - 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) return (changeTimestampMs >= (lastUpdateTimestampMs - ConfigFactory.configChangeBufferPeriod)) } + } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt index f8e64dd381..d89c0b04bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt @@ -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) { val groups = userGroups ?: return - if (!group.isClosedGroup) return + if (!group.isLegacyClosedGroup) return val storage = MessagingModuleConfiguration.shared.storage val threadId = storage.getThreadId(group.encodedId) ?: return val groupPublicKey = GroupUtil.doubleEncodeGroupID(group.getId()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt index ecd40938a1..0b3518ee99 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt @@ -7,6 +7,13 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup 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.view.isVisible 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.groupSizeLimit import org.session.libsession.utilities.Address +import org.session.libsession.utilities.Contact import org.session.libsession.utilities.Device import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient @@ -49,8 +57,11 @@ class CreateGroupFragment : Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - binding = FragmentCreateGroupBinding.inflate(inflater) - return binding.root + return ComposeView(requireContext()).apply { + setContent { + + } + } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -121,4 +132,15 @@ class CreateGroupFragment : Fragment() { context.startActivity(intent) } +} + +@Composable +fun MemberList(contacts: List, modifier: Modifier = Modifier) { + Avatar +} + +@Preview +@Composable +fun ClosedGroupPreview() { + MemberList(contacts = emptyList(), modifier = Modifier.fillMaxWidth()) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index ba519f0a79..0821d294ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -144,7 +144,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(address)) push(intent) } - is GlobalSearchAdapter.Model.GroupConversation -> { + is GlobalSearchAdapter.Model.LegacyGroupConversation -> { val groupAddress = Address.fromSerialized(model.groupRecord.encodedId) val threadId = threadDb.getThreadIdIfExistsFor(Recipient.from(this, groupAddress, false)) if (threadId >= 0) { @@ -257,7 +257,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), globalSearchViewModel.result.collect { result -> val currentUserPublicKey = publicKey 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() @@ -642,14 +642,18 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), // Cancel any outstanding jobs DatabaseComponent.get(context).sessionJobDatabase().cancelPendingMessageSendJobs(threadID) // 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 { GroupUtil.doubleDecodeGroupID(recipient.address.toString()).toHexString() .takeIf(DatabaseComponent.get(context).lokiAPIDatabase()::isClosedGroup) ?.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 val v2OpenGroup = DatabaseComponent.get(context).lokiThreadDatabase().getOpenGroupChat(threadID) if (v2OpenGroup != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt index 7cf953be24..ffa7fba3b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt @@ -9,9 +9,10 @@ import androidx.recyclerview.widget.RecyclerView import network.loki.messenger.R import network.loki.messenger.databinding.ViewGlobalSearchHeaderBinding 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.recipients.Recipient -import org.thoughtcrime.securesms.mms.GlideApp +import org.session.libsignal.utilities.SessionId import org.thoughtcrime.securesms.search.model.MessageResult import java.security.InvalidParameterException 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) { binding.searchResultProfilePicture.recycle() when (model) { - is Model.GroupConversation -> bindModel(query, model) + is Model.LegacyGroupConversation -> bindModel(query, model) is Model.Contact -> bindModel(query, model) is Model.Message -> bindModel(query, 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 SavedMessages(val currentUserPublicKey: String): 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() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt index 5371bb71c9..09b9721461 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt @@ -11,7 +11,7 @@ import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient 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.Message 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.searchResultTitle.text = model.messageResult.conversationRecipient.toShortString() } - is GroupConversation -> { + is LegacyGroupConversation -> { binding.searchResultTitle.text = getHighlight( query, model.groupRecord.title @@ -86,10 +86,10 @@ private fun getHighlight(query: String?, toSearch: String): Spannable? { 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.searchResultSavedMessages.isVisible = false - binding.searchResultSubtitle.isVisible = model.groupRecord.isClosedGroup + binding.searchResultSubtitle.isVisible = model.groupRecord.isLegacyClosedGroup binding.searchResultTimestamp.isVisible = false val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupRecord.encodedId), false) binding.searchResultProfilePicture.update(threadRecipient) @@ -102,7 +102,7 @@ fun ContentView.bindModel(query: String?, model: GroupConversation) { val address = it.address.serialize() it.name ?: "${address.take(4)}...${address.takeLast(4)}" } - if (model.groupRecord.isClosedGroup) { + if (model.groupRecord.isLegacyClosedGroup) { binding.searchResultSubtitle.text = getHighlight(query, membersString) } } @@ -132,11 +132,6 @@ fun ContentView.bindModel(query: String?, model: Message) { binding.searchResultProfilePicture.isVisible = true binding.searchResultSavedMessages.isVisible = false 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.searchResultProfilePicture.update(model.messageResult.conversationRecipient) val textSpannable = SpannableStringBuilder() diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java index 2c70bff637..b26cf87435 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -42,7 +42,6 @@ import com.goterl.lazysodium.utils.KeyPair; import org.session.libsession.messaging.open_groups.OpenGroup; 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.snode.SnodeAPI; 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.libsignal.utilities.IdPrefix; import org.session.libsignal.utilities.Log; +import org.session.libsignal.utilities.SessionId; import org.session.libsignal.utilities.Util; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.contacts.ContactUtil; @@ -555,7 +555,7 @@ public class DefaultMessageNotifier implements MessageNotifier { if (openGroup != null && edKeyPair != null) { KeyPair blindedKeyPair = SodiumUtilities.blindedKeyPair(openGroup.getPublicKey(), edKeyPair); if (blindedKeyPair != null) { - return new SessionId(IdPrefix.BLINDED, blindedKeyPair.getPublicKey().getAsBytes()).getHexString(); + return new SessionId(IdPrefix.BLINDED, blindedKeyPair.getPublicKey().getAsBytes()).hexString(); } } return null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java index 1c05e68bdf..b656eb6207 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java @@ -9,11 +9,10 @@ import android.widget.TextView; import androidx.annotation.NonNull; 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.emoji.EmojiImageView; import org.thoughtcrime.securesms.database.model.MessageId; -import org.thoughtcrime.securesms.mms.GlideApp; import java.util.Collections; import java.util.List; diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index 8b1975865d..f211460b5c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -5,11 +5,11 @@ import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.JobQueue 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.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.IdPrefix +import org.session.libsignal.utilities.SessionId import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt index abbf73ff4b..6d35d2f145 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.util import android.content.Context +import android.provider.Telephony.Sms.Conversations import network.loki.messenger.libsession_util.ConfigBase import network.loki.messenger.libsession_util.Contacts 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.utilities.Hex import org.session.libsignal.utilities.IdPrefix +import org.session.libsignal.utilities.SessionId import org.session.libsignal.utilities.toHexString import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.ThreadDatabase @@ -199,6 +201,11 @@ object ConfigurationMessageUtilities { convoConfig.getOrConstructCommunity(base, room, pubKey) } 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()) convoConfig.getOrConstructLegacyGroup(groupPublicKey) } @@ -241,7 +248,7 @@ object ConfigurationMessageUtilities { } 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 -> val groupAddress = Address.fromSerialized(group.encodedId) val groupPublicKey = GroupUtil.doubleDecodeGroupID(groupAddress.serialize()).toHexString() @@ -252,7 +259,7 @@ object ConfigurationMessageUtilities { 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() GroupInfo.LegacyGroupInfo( - sessionId = groupPublicKey, + sessionId = SessionId.from(groupPublicKey), name = group.title, members = admins + members, priority = if (isPinned) ConfigBase.PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE, diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SharedConfigUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/SharedConfigUtils.kt index b15d82a33e..a259df2e54 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SharedConfigUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SharedConfigUtils.kt @@ -13,6 +13,8 @@ fun ConversationVolatileConfig.getConversationUnread(thread: ThreadRecord): Bool && recipient.address.serialize().startsWith(IdPrefix.STANDARD.value)) { return getOneToOne(recipient.address.serialize())?.unread == true } else if (recipient.isClosedGroupRecipient) { + return getClosedGroup(recipient.address.serialize())?.unread == true + } else if (recipient.isLegacyClosedGroupRecipient) { return getLegacyClosedGroup(GroupUtil.doubleDecodeGroupId(recipient.address.toGroupString()))?.unread == true } else if (recipient.isOpenGroupRecipient) { val openGroup = MessagingModuleConfiguration.shared.storage.getOpenGroup(thread.threadId) ?: return false diff --git a/libsession-util/src/androidTest/java/network/loki/messenger/libsession_util/InstrumentedTests.kt b/libsession-util/src/androidTest/java/network/loki/messenger/libsession_util/InstrumentedTests.kt index 3f5bee9049..07bb0ef871 100644 --- a/libsession-util/src/androidTest/java/network/loki/messenger/libsession_util/InstrumentedTests.kt +++ b/libsession-util/src/androidTest/java/network/loki/messenger/libsession_util/InstrumentedTests.kt @@ -601,7 +601,7 @@ class InstrumentedTests { assertEquals("New Group", infoConf.getName()) infoConf.setCreated(System.currentTimeMillis()) assertThat(infoConf.getCreated(), notNullValue()) - val memberConf = GroupMemberConfig.newInstance(groupPublic, groupSecret) + val memberConf = GroupMembersConfig.newInstance(groupPublic, groupSecret) memberConf.set( GroupMember( sessionId = "05"+Hex.toStringCondensed(userCurve), @@ -637,7 +637,7 @@ class InstrumentedTests { assertEquals("New Group", infoConf.getName()) infoConf.setCreated(System.currentTimeMillis()) assertThat(infoConf.getCreated(), notNullValue()) - val memberConf = GroupMemberConfig.newInstance(groupPublic, groupSecret) + val memberConf = GroupMembersConfig.newInstance(groupPublic, groupSecret) memberConf.set( GroupMember( sessionId = "05"+Hex.toStringCondensed(userCurve), diff --git a/libsession-util/src/main/cpp/group_info.cpp b/libsession-util/src/main/cpp/group_info.cpp index 491a5b04b1..e72fee45c7 100644 --- a/libsession-util/src/main/cpp/group_info.cpp +++ b/libsession-util/src/main/cpp/group_info.cpp @@ -177,3 +177,11 @@ Java_network_loki_messenger_libsession_1util_GroupInfoConfig_storageNamespace(JN auto group_info = ptrToInfo(env, thiz); return static_cast(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); +} \ No newline at end of file diff --git a/libsession-util/src/main/cpp/group_keys.cpp b/libsession-util/src/main/cpp/group_keys.cpp index 3a1b13ed8d..eb5a8b867a 100644 --- a/libsession-util/src/main/cpp/group_keys.cpp +++ b/libsession-util/src/main/cpp/group_keys.cpp @@ -65,6 +65,7 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_groupKeys(JNIEnv *e extern "C" JNIEXPORT void JNICALL Java_network_loki_messenger_libsession_1util_GroupKeysConfig_loadKey(JNIEnv *env, jobject thiz, + jstring hash, jbyteArray data, jbyteArray msg_id, jlong timestamp_ms, @@ -72,11 +73,13 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_loadKey(JNIEnv *env jobject members_jobject) { std::lock_guard lock{util::util_mutex_}; auto keys = ptrToKeys(env, thiz); + auto hash_bytes = env->GetStringUTFChars(hash, nullptr); auto data_bytes = util::ustring_from_bytes(env, data); auto msg_bytes = util::ustring_from_bytes(env, msg_id); auto info = ptrToInfo(env, info_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" diff --git a/libsession-util/src/main/cpp/group_members.cpp b/libsession-util/src/main/cpp/group_members.cpp index 3c758b18f3..34395a9edd 100644 --- a/libsession-util/src/main/cpp/group_members.cpp +++ b/libsession-util/src/main/cpp/group_members.cpp @@ -2,7 +2,7 @@ extern "C" 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, jbyteArray initial_dump) { 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); - 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, "", "(J)V"); jobject newConfig = env->NewObject(groupMemberClass, constructor, reinterpret_cast(group_members)); @@ -29,7 +29,7 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_00024Companion_ne extern "C" 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_}; auto config = ptrToMembers(env, thiz); jclass stack = env->FindClass("java/util/Stack"); @@ -45,8 +45,8 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_all(JNIEnv *env, extern "C" JNIEXPORT jboolean JNICALL -Java_network_loki_messenger_libsession_1util_GroupMemberConfig_erase(JNIEnv *env, jobject thiz, - jobject group_member) { +Java_network_loki_messenger_libsession_1util_GroupMembersConfig_erase(JNIEnv *env, jobject thiz, + jobject group_member) { auto config = ptrToMembers(env, thiz); auto member = util::deserialize_group_member(env, group_member); return config->erase(member.session_id); @@ -54,8 +54,8 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_erase(JNIEnv *env extern "C" JNIEXPORT jobject JNICALL -Java_network_loki_messenger_libsession_1util_GroupMemberConfig_get(JNIEnv *env, jobject thiz, - jstring pub_key_hex) { +Java_network_loki_messenger_libsession_1util_GroupMembersConfig_get(JNIEnv *env, jobject thiz, + jstring pub_key_hex) { std::lock_guard lock{util::util_mutex_}; auto config = ptrToMembers(env, thiz); 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" JNIEXPORT jobject JNICALL -Java_network_loki_messenger_libsession_1util_GroupMemberConfig_getOrConstruct(JNIEnv *env, - jobject thiz, - jstring pub_key_hex) { +Java_network_loki_messenger_libsession_1util_GroupMembersConfig_getOrConstruct(JNIEnv *env, + jobject thiz, + jstring pub_key_hex) { std::lock_guard lock{util::util_mutex_}; auto config = ptrToMembers(env, thiz); 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" JNIEXPORT void JNICALL -Java_network_loki_messenger_libsession_1util_GroupMemberConfig_set(JNIEnv *env, jobject thiz, - jobject group_member) { +Java_network_loki_messenger_libsession_1util_GroupMembersConfig_set(JNIEnv *env, jobject thiz, + jobject group_member) { std::lock_guard lock{util::util_mutex_}; auto config = ptrToMembers(env, thiz); auto deserialized = util::deserialize_group_member(env, group_member); diff --git a/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt b/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt index daef7aacd7..a36c170615 100644 --- a/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt +++ b/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt @@ -10,6 +10,8 @@ import network.loki.messenger.libsession_util.util.UserPic import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.SessionId +import java.io.Closeable import java.util.Stack @@ -27,7 +29,7 @@ sealed class ConfigBase(protected val /* yucky */ pointer: Long) { is UserGroupsConfig -> Kind.GROUPS is GroupInfoConfig -> Kind.CLOSED_GROUP_INFO 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) @@ -217,7 +219,7 @@ class UserGroupsConfig(pointer: Long): ConfigBase(pointer) { external fun createGroup(): GroupInfo.ClosedGroupInfo } -class GroupInfoConfig(pointer: Long): ConfigBase(pointer) { +class GroupInfoConfig(pointer: Long): ConfigBase(pointer), Closeable { companion object { init { System.loadLibrary("session_util") @@ -229,6 +231,7 @@ class GroupInfoConfig(pointer: Long): ConfigBase(pointer) { initialDump: ByteArray = byteArrayOf() ): GroupInfoConfig } + external fun id(): SessionId external fun destroyGroup() external fun getCreated(): Long? external fun getDeleteAttachmentsBefore(): Long? @@ -244,9 +247,12 @@ class GroupInfoConfig(pointer: Long): ConfigBase(pointer) { external fun setName(newName: String) external fun setProfilePic(newProfilePic: UserPic) external fun storageNamespace(): Long + override fun close() { + free() + } } -class GroupMemberConfig(pointer: Long): ConfigBase(pointer) { +class GroupMembersConfig(pointer: Long): ConfigBase(pointer), Closeable { companion object { init { System.loadLibrary("session_util") @@ -255,16 +261,19 @@ class GroupMemberConfig(pointer: Long): ConfigBase(pointer) { pubKey: ByteArray, secretKey: ByteArray = byteArrayOf(), initialDump: ByteArray = byteArrayOf() - ): GroupMemberConfig + ): GroupMembersConfig } external fun all(): Stack external fun erase(groupMember: GroupMember): Boolean external fun get(pubKeyHex: String): GroupMember? external fun getOrConstruct(pubKeyHex: String): 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 { init { System.loadLibrary("session_util") @@ -275,17 +284,21 @@ class GroupKeysConfig(pointer: Long): ConfigBase(pointer) { groupSecretKey: ByteArray = byteArrayOf(), initialDump: ByteArray = byteArrayOf(), info: GroupInfoConfig, - members: GroupMemberConfig + members: GroupMembersConfig ): GroupKeysConfig } external fun groupKeys(): Stack - external fun loadKey(data: ByteArray, + external fun loadKey(hash: String, + data: ByteArray, msgId: ByteArray, timestampMs: Long, info: GroupInfoConfig, - members: GroupMemberConfig) + members: GroupMembersConfig) external fun needsRekey(): Boolean external fun pendingKey(): 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() + } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index dbf03832b4..016686bda1 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -154,7 +154,7 @@ interface StorageProtocol { fun setExpirationTimer(address: String, duration: Int) // Closed Groups - fun getMembers(groupPublicKey: String): List + fun getMembers(groupPublicKey: String): List // Groups fun getAllGroups(includeInactive: Boolean): List diff --git a/libsession/src/main/java/org/session/libsession/utilities/Address.kt b/libsession/src/main/java/org/session/libsession/utilities/Address.kt index 805166e1cb..b029f597ef 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/Address.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/Address.kt @@ -23,6 +23,8 @@ class Address private constructor(address: String) : Parcelable, Comparable fun persist(forConfigObject: ConfigBase, timestamp: Long) diff --git a/libsession/src/main/java/org/session/libsession/utilities/recipients/Recipient.java b/libsession/src/main/java/org/session/libsession/utilities/recipients/Recipient.java index 9d2c8f150f..b151afd70d 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/recipients/Recipient.java +++ b/libsession/src/main/java/org/session/libsession/utilities/recipients/Recipient.java @@ -476,6 +476,10 @@ public class Recipient implements RecipientModifiedListener { return address.isLegacyClosedGroup(); } + public boolean isClosedGroupRecipient() { + return address.isClosedGroup(); + } + @Deprecated public boolean isPushGroupRecipient() {