diff --git a/app/build.gradle b/app/build.gradle
index 3504681917..c4944290a6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -104,8 +104,8 @@ dependencies {
implementation project(":libsignal")
implementation project(":libsession")
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxJsonVersion"
- implementation "org.whispersystems:curve25519-java:$curve25519Version"
- implementation 'com.goterl:lazysodium-android:5.0.2@aar'
+ implementation "com.github.oxen-io.session-android-curve-25519:curve25519-java:$curve25519Version"
+ implementation project(":liblazysodium")
implementation "net.java.dev.jna:jna:5.8.0@aar"
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion"
diff --git a/app/src/androidTest/java/network/loki/messenger/SodiumUtilitiesTest.kt b/app/src/androidTest/java/network/loki/messenger/SodiumUtilitiesTest.kt
new file mode 100644
index 0000000000..af260a0bf0
--- /dev/null
+++ b/app/src/androidTest/java/network/loki/messenger/SodiumUtilitiesTest.kt
@@ -0,0 +1,166 @@
+package network.loki.messenger
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.goterl.lazysodium.utils.Key
+import com.goterl.lazysodium.utils.KeyPair
+import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.session.libsession.messaging.utilities.SodiumUtilities
+import org.session.libsignal.utilities.Base64
+import org.session.libsignal.utilities.Hex
+import org.session.libsignal.utilities.toHexString
+
+@RunWith(AndroidJUnit4::class)
+class SodiumUtilitiesTest {
+
+ private val publicKey: String = "88672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b"
+ private val privateKey: String = "30d796c1ddb4dc455fd998a98aa275c247494a9a7bde9c1fee86ae45cd585241"
+ private val edKeySeed: String = "c010d89eccbaf5d1c6d19df766c6eedf965d4a28a56f87c9fc819edb59896dd9"
+ private val edPublicKey: String = "bac6e71efd7dfa4a83c98ed24f254ab2c267f9ccdb172a5280a0444ad24e89cc"
+ private val edSecretKey: String = "c010d89eccbaf5d1c6d19df766c6eedf965d4a28a56f87c9fc819edb59896dd9bac6e71efd7dfa4a83c98ed24f254ab2c267f9ccdb172a5280a0444ad24e89cc"
+ private val blindedPublicKey: String = "98932d4bccbe595a8789d7eb1629cefc483a0eaddc7e20e8fe5c771efafd9af5"
+ private val serverPublicKey: String = "c3b3c6f32f0ab5a57f853cc4f30f5da7fda5624b0c77b3fb0829de562ada081d"
+
+ private val edKeyPair = KeyPair(Key.fromHexString(edPublicKey), Key.fromHexString(edSecretKey))
+
+ @Test
+ fun generateBlindingFactorSuccess() {
+ val result = SodiumUtilities.generateBlindingFactor(serverPublicKey)
+
+ assertThat(result?.toHexString(), equalTo("84e3eb75028a9b73fec031b7448e322a68ca6485fad81ab1bead56f759ebeb0f"))
+ }
+
+ @Test
+ fun generateBlindingFactorFailure() {
+ val result = SodiumUtilities.generateBlindingFactor("Test")
+
+ assertNull(result?.toHexString())
+ }
+
+ @Test
+ fun blindedKeyPairSuccess() {
+ val result = SodiumUtilities.blindedKeyPair(serverPublicKey, edKeyPair)!!
+
+ assertThat(result.publicKey.asHexString.lowercase(), equalTo(blindedPublicKey))
+ assertThat(result.secretKey.asHexString.take(64).lowercase(), equalTo("16663322d6b684e1c9dcc02b9e8642c3affd3bc431a9ea9e63dbbac88ce7a305"))
+ }
+
+ @Test
+ fun blindedKeyPairFailurePublicKeyLength() {
+ val result = SodiumUtilities.blindedKeyPair(
+ serverPublicKey,
+ KeyPair(Key.fromHexString(edPublicKey.take(4)), Key.fromHexString(edKeySeed))
+ )
+
+ assertNull(result)
+ }
+
+ @Test
+ fun blindedKeyPairFailureSecretKeyLength() {
+ val result = SodiumUtilities.blindedKeyPair(
+ serverPublicKey,
+ KeyPair(Key.fromHexString(edPublicKey), Key.fromHexString(edSecretKey.take(4)))
+ )
+
+ assertNull(result)
+ }
+
+ @Test
+ fun blindedKeyPairFailureBlindingFactor() {
+ val result = SodiumUtilities.blindedKeyPair("Test", edKeyPair)
+
+ assertNull(result)
+ }
+
+ @Test
+ fun sogsSignature() {
+ val expectedSignature = "dcc086abdd2a740d9260b008fb37e12aa0ff47bd2bd9e177bbbec37fd46705a9072ce747bda66c788c3775cdd7ad60ad15a478e0886779aad5d795fd7bf8350d"
+
+ val result = SodiumUtilities.sogsSignature(
+ "TestMessage".toByteArray(),
+ Hex.fromStringCondensed(edSecretKey),
+ Hex.fromStringCondensed("44d82cc15c0a5056825cae7520b6b52d000a23eb0c5ed94c4be2d9dc41d2d409"),
+ Hex.fromStringCondensed("0bb7815abb6ba5142865895f3e5286c0527ba4d31dbb75c53ce95e91ffe025a2")
+ )
+
+ assertThat(result?.toHexString(), equalTo(expectedSignature))
+ }
+
+ @Test
+ fun combineKeysSuccess() {
+ val result = SodiumUtilities.combineKeys(
+ Hex.fromStringCondensed(edSecretKey),
+ Hex.fromStringCondensed(edPublicKey)
+ )
+
+ assertThat(result?.toHexString(), equalTo("1159b5d0fcfba21228eb2121a0f59712fa8276fc6e5547ff519685a40b9819e6"))
+ }
+
+ @Test
+ fun combineKeysFailure() {
+ val result = SodiumUtilities.combineKeys(
+ SodiumUtilities.generatePrivateKeyScalar(Hex.fromStringCondensed(edSecretKey))!!,
+ Hex.fromStringCondensed(publicKey)
+ )
+
+ assertNull(result?.toHexString())
+ }
+
+ @Test
+ fun sharedBlindedEncryptionKeySuccess() {
+ val result = SodiumUtilities.sharedBlindedEncryptionKey(
+ Hex.fromStringCondensed(edSecretKey),
+ Hex.fromStringCondensed(blindedPublicKey),
+ Hex.fromStringCondensed(publicKey),
+ Hex.fromStringCondensed(blindedPublicKey)
+ )
+
+ assertThat(result?.toHexString(), equalTo("388ee09e4c356b91f1cce5cc0aa0cf59e8e8cade69af61685d09c2d2731bc99e"))
+ }
+
+ @Test
+ fun sharedBlindedEncryptionKeyFailure() {
+ val result = SodiumUtilities.sharedBlindedEncryptionKey(
+ Hex.fromStringCondensed(edSecretKey),
+ Hex.fromStringCondensed(publicKey),
+ Hex.fromStringCondensed(edPublicKey),
+ Hex.fromStringCondensed(publicKey)
+ )
+
+ assertNull(result?.toHexString())
+ }
+
+ @Test
+ fun sessionIdSuccess() {
+ val result = SodiumUtilities.sessionId("05$publicKey", "15$blindedPublicKey", serverPublicKey)
+
+ assertTrue(result)
+ }
+
+ @Test
+ fun sessionIdFailureInvalidSessionId() {
+ val result = SodiumUtilities.sessionId("AB$publicKey", "15$blindedPublicKey", serverPublicKey)
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun sessionIdFailureInvalidBlindedId() {
+ val result = SodiumUtilities.sessionId("05$publicKey", "AB$blindedPublicKey", serverPublicKey)
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun sessionIdFailureBlindingFactor() {
+ val result = SodiumUtilities.sessionId("05$publicKey", "15$blindedPublicKey", "Test")
+
+ assertFalse(result)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt
index 6265f2d1f5..4797bb3edd 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt
@@ -15,6 +15,7 @@ import org.session.libsession.avatars.ProfileContactPhoto
import org.session.libsession.avatars.ResourceContactPhoto
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.Address
+import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.mms.GlideRequests
@@ -57,6 +58,11 @@ class ProfilePictureView @JvmOverloads constructor(
val apk = members.getOrNull(1)?.serialize() ?: ""
additionalPublicKey = apk
additionalDisplayName = getUserDisplayName(apk)
+ } else if(recipient.isOpenGroupInboxRecipient) {
+ val publicKey = GroupUtil.getDecodedOpenGroupInbox(recipient.address.serialize())
+ this.publicKey = publicKey
+ displayName = getUserDisplayName(publicKey)
+ additionalPublicKey = null
} else {
val publicKey = recipient.address.toString()
this.publicKey = publicKey
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
index 047e65e48f..236c01c688 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
@@ -58,19 +58,22 @@ import org.session.libsession.messaging.messages.control.DataExtractionNotificat
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
import org.session.libsession.messaging.messages.visible.VisibleMessage
-import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
+import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
+import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Address.Companion.fromSerialized
+import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.MediaTypes
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.concurrent.SimpleTask
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsession.utilities.recipients.RecipientModifiedListener
import org.session.libsignal.crypto.MnemonicCodec
+import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.ListenableFuture
import org.session.libsignal.utilities.guava.Optional
import org.session.libsignal.utilities.hexEncodedPrivateKey
@@ -109,6 +112,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase
import org.thoughtcrime.securesms.database.MmsSmsDatabase
import org.thoughtcrime.securesms.database.SessionContactDatabase
import org.thoughtcrime.securesms.database.SmsDatabase
+import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
@@ -167,6 +171,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
@Inject lateinit var smsDb: SmsDatabase
@Inject lateinit var mmsDb: MmsDatabase
@Inject lateinit var lokiMessageDb: LokiMessageDatabase
+ @Inject lateinit var storage: Storage
@Inject lateinit var viewModelFactory: ConversationViewModel.AssistedFactory
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
@@ -177,9 +182,25 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private val viewModel: ConversationViewModel by viewModels {
var threadId = intent.getLongExtra(THREAD_ID, -1L)
if (threadId == -1L) {
- intent.getParcelableExtra
(ADDRESS)?.let { address ->
- val recipient = Recipient.from(this, address, false)
- threadId = threadDb.getOrCreateThreadIdFor(recipient)
+ intent.getParcelableExtra(ADDRESS)?.let { it ->
+ threadId = threadDb.getThreadIdIfExistsFor(it.serialize())
+ if (threadId == -1L) {
+ val sessionId = SessionId(it.serialize())
+ val openGroup = lokiThreadDb.getOpenGroupChat(intent.getLongExtra(FROM_GROUP_THREAD_ID, -1))
+ val address = if (sessionId.prefix == IdPrefix.BLINDED && openGroup != null) {
+ storage.getOrCreateBlindedIdMapping(sessionId.hexString, openGroup.server, openGroup.publicKey).sessionId?.let {
+ fromSerialized(it)
+ } ?: run {
+ val openGroupInboxId =
+ "${openGroup.server}!${openGroup.publicKey}!${sessionId.hexString}".toByteArray()
+ fromSerialized(GroupUtil.getEncodedOpenGroupInboxID(openGroupInboxId))
+ }
+ } else {
+ it
+ }
+ val recipient = Recipient.from(this, address, false)
+ threadId = threadDb.getOrCreateThreadIdFor(recipient)
+ }
} ?: finish()
}
viewModelFactory.create(threadId)
@@ -263,6 +284,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// Extras
const val THREAD_ID = "thread_id"
const val ADDRESS = "address"
+ const val FROM_GROUP_THREAD_ID = "from_group_thread_id"
const val SCROLL_MESSAGE_ID = "scroll_message_id"
const val SCROLL_MESSAGE_AUTHOR = "scroll_message_author"
// Request codes
@@ -508,7 +530,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private fun getLatestOpenGroupInfoIfNeeded() {
val openGroup = lokiThreadDb.getOpenGroupChat(viewModel.threadId) ?: return
- OpenGroupAPIV2.getMemberCount(openGroup.room, openGroup.server).successUi { updateSubtitle() }
+ OpenGroupApi.getMemberCount(openGroup.room, openGroup.server).successUi { updateSubtitle() }
}
// called from onCreate
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt
index a72ee0daf8..834b77eccb 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt
@@ -7,7 +7,7 @@ import android.view.View
import android.widget.LinearLayout
import network.loki.messenger.databinding.ViewMentionCandidateBinding
import org.session.libsession.messaging.mentions.Mention
-import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
+import org.thoughtcrime.securesms.groups.OpenGroupManager
import org.thoughtcrime.securesms.mms.GlideRequests
class MentionCandidateView : LinearLayout {
@@ -34,7 +34,7 @@ class MentionCandidateView : LinearLayout {
profilePictureView.root.glide = glide!!
profilePictureView.root.update()
if (openGroupServer != null && openGroupRoom != null) {
- val isUserModerator = OpenGroupAPIV2.isUserModerator(mentionCandidate.publicKey, openGroupRoom!!, openGroupServer!!)
+ val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", mentionCandidate.publicKey)
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
} else {
moderatorIconImageView.visibility = View.GONE
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt
index 83cd2a250f..a21ba1b502 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt
@@ -7,7 +7,7 @@ import android.view.View
import android.widget.RelativeLayout
import network.loki.messenger.databinding.ViewMentionCandidateV2Binding
import org.session.libsession.messaging.mentions.Mention
-import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
+import org.thoughtcrime.securesms.groups.OpenGroupManager
import org.thoughtcrime.securesms.mms.GlideRequests
class MentionCandidateView : RelativeLayout {
@@ -34,7 +34,7 @@ class MentionCandidateView : RelativeLayout {
profilePictureView.root.glide = glide!!
profilePictureView.root.update()
if (openGroupServer != null && openGroupRoom != null) {
- val isUserModerator = OpenGroupAPIV2.isUserModerator(candidate.publicKey, openGroupRoom!!, openGroupServer!!)
+ val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", candidate.publicKey)
moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE
} else {
moderatorIconImageView.visibility = View.GONE
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt
index 275691c002..ffd3a41bfd 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt
@@ -5,13 +5,17 @@ import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import network.loki.messenger.R
-import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
+import org.session.libsession.messaging.MessagingModuleConfiguration
+import org.session.libsession.messaging.utilities.SessionId
+import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.utilities.TextSecurePreferences
+import org.session.libsignal.utilities.IdPrefix
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.conversation.v2.ConversationAdapter
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
+import org.thoughtcrime.securesms.groups.OpenGroupManager
class ConversationActionModeCallback(private val adapter: ConversationAdapter, private val threadID: Long,
private val context: Context) : ActionMode.Callback {
@@ -34,6 +38,9 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
val openGroup = DatabaseComponent.get(context).lokiThreadDatabase().getOpenGroupChat(threadID)
val thread = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(threadID)!!
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
+ val edKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!
+ val blindedPublicKey = openGroup?.publicKey?.let { SodiumUtilities.blindedKeyPair(it, edKeyPair)?.publicKey?.asBytes }
+ ?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString
fun userCanDeleteSelectedItems(): Boolean {
val allSentByCurrentUser = selectedItems.all { it.isOutgoing }
@@ -41,13 +48,13 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
if (!ConversationActivityV2.IS_UNSEND_REQUESTS_ENABLED) {
if (openGroup == null) { return true }
if (allSentByCurrentUser) { return true }
- return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server)
+ return OpenGroupManager.isUserModerator(context, openGroup.groupId, userPublicKey, blindedPublicKey)
}
val allReceivedByCurrentUser = selectedItems.all { !it.isOutgoing }
if (openGroup == null) { return allSentByCurrentUser || allReceivedByCurrentUser }
if (allSentByCurrentUser) { return true }
- return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server)
+ return OpenGroupManager.isUserModerator(context, openGroup.groupId, userPublicKey, blindedPublicKey)
}
fun userCanBanSelectedUsers(): Boolean {
if (openGroup == null) { return false }
@@ -55,7 +62,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
if (anySentByCurrentUser) { return false } // Users can't ban themselves
val selectedUsers = selectedItems.map { it.recipient.address.toString() }.toSet()
if (selectedUsers.size > 1) { return false }
- return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server)
+ return OpenGroupManager.isUserModerator(context, openGroup.groupId, userPublicKey, blindedPublicKey)
}
// Delete message
menu.findItem(R.id.menu_context_delete_message).isVisible = userCanDeleteSelectedItems()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt
index a8527b09b1..be107c03d9 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt
@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.conversation.v2.messages
import android.content.Context
+import android.content.Intent
import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Rect
@@ -23,16 +24,19 @@ import network.loki.messenger.R
import network.loki.messenger.databinding.ViewVisibleMessageBinding
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.contacts.Contact.ContactContext
-import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
+import org.session.libsession.utilities.Address
import org.session.libsession.utilities.ViewUtil
+import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.ThreadUtils
import org.thoughtcrime.securesms.ApplicationContext
+import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.database.LokiThreadDatabase
import org.thoughtcrime.securesms.database.MmsDatabase
import org.thoughtcrime.securesms.database.MmsSmsDatabase
import org.thoughtcrime.securesms.database.SmsDatabase
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.MessageRecord
+import org.thoughtcrime.securesms.groups.OpenGroupManager
import org.thoughtcrime.securesms.home.UserDetailsBottomSheet
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.DateUtils
@@ -140,15 +144,27 @@ class VisibleMessageView : LinearLayout {
binding.profilePictureView.root.glide = glide
binding.profilePictureView.root.update(message.individualRecipient)
binding.profilePictureView.root.setOnClickListener {
- showUserDetails(senderSessionID, threadID)
+ if (thread.isOpenGroupRecipient) {
+ if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED) {
+ val intent = Intent(context, ConversationActivityV2::class.java)
+ intent.putExtra(ConversationActivityV2.FROM_GROUP_THREAD_ID, threadID)
+ intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(senderSessionID))
+ context.startActivity(intent)
+ }
+ } else {
+ maybeShowUserDetails(senderSessionID, threadID)
+ }
}
if (thread.isOpenGroupRecipient) {
val openGroup = lokiThreadDb.getOpenGroupChat(threadID) ?: return
- val isModerator = OpenGroupAPIV2.isUserModerator(
- senderSessionID,
- openGroup.room,
- openGroup.server
- )
+ var standardPublicKey = ""
+ var blindedPublicKey: String? = null
+ if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED) {
+ blindedPublicKey = senderSessionID
+ } else {
+ standardPublicKey = senderSessionID
+ }
+ val isModerator = OpenGroupManager.isUserModerator(context, openGroup.groupId, standardPublicKey, blindedPublicKey)
binding.moderatorIconImageView.isVisible = !message.isOutgoing && isModerator
}
}
@@ -403,7 +419,7 @@ class VisibleMessageView : LinearLayout {
pressCallback = null
}
- private fun showUserDetails(publicKey: String, threadID: Long) {
+ private fun maybeShowUserDetails(publicKey: String, threadID: Long) {
val userDetailsBottomSheet = UserDetailsBottomSheet()
val bundle = bundleOf(
UserDetailsBottomSheet.ARGUMENT_PUBLIC_KEY to publicKey,
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt
index 9dafdcf878..11b2c6f6d1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/MentionUtilities.kt
@@ -11,6 +11,7 @@ import androidx.core.content.res.ResourcesCompat
import network.loki.messenger.R
import nl.komponents.kovenant.combine.Tuple2
import org.session.libsession.messaging.contacts.Contact
+import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.util.UiModeUtilities
@@ -20,39 +21,27 @@ object MentionUtilities {
@JvmStatic
fun highlightMentions(text: CharSequence, threadID: Long, context: Context): String {
- val threadDB = DatabaseComponent.get(context).threadDatabase()
- val isOpenGroup = threadDB.getRecipientForThreadId(threadID)?.isOpenGroupRecipient ?: false
- return highlightMentions(text, false, isOpenGroup, context).toString() // isOutgoingMessage is irrelevant
- }
-
- @JvmStatic
- fun highlightMentions(text:CharSequence, isOpenGroup: Boolean, context: Context): String {
- return highlightMentions(text, false, isOpenGroup, context).toString()
+ return highlightMentions(text, false, threadID, context).toString() // isOutgoingMessage is irrelevant
}
@JvmStatic
fun highlightMentions(text: CharSequence, isOutgoingMessage: Boolean, threadID: Long, context: Context): SpannableString {
- val threadDB = DatabaseComponent.get(context).threadDatabase()
- val isOpenGroup = threadDB.getRecipientForThreadId(threadID)?.isOpenGroupRecipient ?: false
- return highlightMentions(text, isOutgoingMessage, isOpenGroup, context) // isOutgoingMessage is irrelevant
- }
-
- @JvmStatic
- fun highlightMentions(text: CharSequence, isOutgoingMessage: Boolean, isOpenGroup: Boolean, context: Context): SpannableString {
@Suppress("NAME_SHADOWING") var text = text
val pattern = Pattern.compile("@[0-9a-fA-F]*")
var matcher = pattern.matcher(text)
val mentions = mutableListOf, String>>()
var startIndex = 0
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
+ val openGroup = DatabaseComponent.get(context).storage().getOpenGroup(threadID)
if (matcher.find(startIndex)) {
while (true) {
val publicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @
- val userDisplayName: String? = if (publicKey.equals(userPublicKey, ignoreCase = true)) {
- TextSecurePreferences.getProfileName(context)
+ val isUserBlindedPublicKey = openGroup?.let { SodiumUtilities.sessionId(userPublicKey, publicKey, it.publicKey) } ?: false
+ val userDisplayName: String? = if (publicKey.equals(userPublicKey, ignoreCase = true) || isUserBlindedPublicKey) {
+ context.getString(R.string.MessageRecord_you)
} else {
val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithSessionID(publicKey)
- @Suppress("NAME_SHADOWING") val context = if (isOpenGroup) Contact.ContactContext.OPEN_GROUP else Contact.ContactContext.REGULAR
+ @Suppress("NAME_SHADOWING") val context = if (openGroup != null) Contact.ContactContext.OPEN_GROUP else Contact.ContactContext.REGULAR
contact?.displayName(context)
}
if (userDisplayName != null) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/BlindedIdMappingDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/BlindedIdMappingDatabase.kt
new file mode 100644
index 0000000000..950c1c6bcf
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/BlindedIdMappingDatabase.kt
@@ -0,0 +1,88 @@
+package org.thoughtcrime.securesms.database
+
+import android.content.ContentValues
+import android.content.Context
+import android.database.Cursor
+import androidx.core.database.getStringOrNull
+import org.session.libsession.messaging.BlindedIdMapping
+import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
+
+class BlindedIdMappingDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
+
+ companion object {
+ const val TABLE_NAME = "blinded_id_mapping"
+ const val ROW_ID = "_id"
+ const val BLINDED_PK = "blinded_pk"
+ const val SESSION_PK = "session_pk"
+ const val SERVER_URL = "server_url"
+ const val SERVER_PK = "server_pk"
+
+ @JvmField
+ val CREATE_BLINDED_ID_MAPPING_TABLE_COMMAND = """
+ CREATE TABLE $TABLE_NAME (
+ $ROW_ID INTEGER PRIMARY KEY,
+ $BLINDED_PK TEXT NOT NULL,
+ $SESSION_PK TEXT DEFAULT NULL,
+ $SERVER_URL TEXT NOT NULL,
+ $SERVER_PK TEXT NOT NULL
+ )
+ """.trimIndent()
+
+ private fun readBlindedIdMapping(cursor: Cursor): BlindedIdMapping {
+ return BlindedIdMapping(
+ blindedId = cursor.getString(cursor.getColumnIndexOrThrow(BLINDED_PK)),
+ sessionId = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(SESSION_PK)),
+ serverUrl = cursor.getString(cursor.getColumnIndexOrThrow(SERVER_URL)),
+ serverId = cursor.getString(cursor.getColumnIndexOrThrow(SERVER_PK)),
+ )
+ }
+ }
+
+ fun getBlindedIdMapping(blindedId: String): List {
+ val query = "$BLINDED_PK = ?"
+ val args = arrayOf(blindedId)
+
+ val mappings: MutableList = mutableListOf()
+
+ readableDatabase.query(TABLE_NAME, null, query, args, null, null, null).use { cursor ->
+ while (cursor.moveToNext()) {
+ mappings += readBlindedIdMapping(cursor)
+ }
+ }
+
+ return mappings
+ }
+
+ fun addBlindedIdMapping(blindedIdMapping: BlindedIdMapping) {
+ writableDatabase.beginTransaction()
+ try {
+ val values = ContentValues().apply {
+ put(BLINDED_PK, blindedIdMapping.blindedId)
+ put(SERVER_PK, blindedIdMapping.sessionId)
+ put(SERVER_URL, blindedIdMapping.serverUrl)
+ put(SERVER_PK, blindedIdMapping.serverId)
+ }
+
+ writableDatabase.insert(TABLE_NAME, null, values)
+ writableDatabase.setTransactionSuccessful()
+ } finally {
+ writableDatabase.endTransaction()
+ }
+ }
+
+ fun getBlindedIdMappingsExceptFor(server: String): List {
+ val query = "$SESSION_PK IS NOT NULL AND $SERVER_URL <> ?"
+ val args = arrayOf(server)
+
+ val mappings: MutableList = mutableListOf()
+
+ readableDatabase.query(TABLE_NAME, null, query, args, null, null, null).use { cursor ->
+ while (cursor.moveToNext()) {
+ mappings += readBlindedIdMapping(cursor)
+ }
+ }
+
+ return mappings
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java
index a6db149053..b984a6e0f2 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java
@@ -23,6 +23,8 @@ import android.database.Cursor;
import androidx.annotation.NonNull;
+import net.sqlcipher.database.SQLiteDatabase;
+
import org.session.libsession.utilities.WindowDebouncer;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
@@ -92,4 +94,12 @@ public abstract class Database {
this.databaseHelper = databaseHelper;
}
+ protected SQLiteDatabase getReadableDatabase() {
+ return databaseHelper.getReadableDatabase();
+ }
+
+ protected SQLiteDatabase getWritableDatabase() {
+ return databaseHelper.getWritableDatabase();
+ }
+
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupMemberDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/GroupMemberDatabase.kt
new file mode 100644
index 0000000000..878b8a1c09
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupMemberDatabase.kt
@@ -0,0 +1,72 @@
+package org.thoughtcrime.securesms.database
+
+import android.content.ContentValues
+import android.content.Context
+import android.database.Cursor
+import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
+import org.session.libsession.messaging.open_groups.GroupMember
+import org.session.libsession.messaging.open_groups.GroupMemberRole
+
+class GroupMemberDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
+
+ companion object {
+ const val TABLE_NAME = "group_member"
+ const val GROUP_ID = "group_id"
+ const val PROFILE_ID = "profile_id"
+ const val ROLE = "role"
+
+ private val allColumns = arrayOf(GROUP_ID, PROFILE_ID, ROLE)
+
+ @JvmField
+ val CREATE_GROUP_MEMBER_TABLE_COMMAND = """
+ CREATE TABLE $TABLE_NAME (
+ $GROUP_ID TEXT NOT NULL,
+ $PROFILE_ID TEXT NOT NULL,
+ $ROLE TEXT NOT NULL,
+ PRIMARY KEY ($GROUP_ID, $PROFILE_ID)
+ )
+ """.trimIndent()
+
+ private fun readGroupMember(cursor: Cursor): GroupMember {
+ return GroupMember(
+ groupId = cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)),
+ profileId = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_ID)),
+ role = GroupMemberRole.valueOf(cursor.getString(cursor.getColumnIndexOrThrow(ROLE))),
+ )
+ }
+ }
+
+ fun getGroupMemberRoles(groupId: String, profileId: String): List {
+ val query = "$GROUP_ID = ? AND $PROFILE_ID = ?"
+ val args = arrayOf(groupId, profileId)
+
+ val mappings: MutableList = mutableListOf()
+
+ readableDatabase.query(TABLE_NAME, allColumns, query, args, null, null, null).use { cursor ->
+ while (cursor.moveToNext()) {
+ mappings += readGroupMember(cursor)
+ }
+ }
+
+ return mappings.map { it.role }
+ }
+
+ fun addGroupMember(member: GroupMember) {
+ writableDatabase.beginTransaction()
+ try {
+ val values = ContentValues().apply {
+ put(GROUP_ID, member.groupId)
+ put(PROFILE_ID, member.profileId)
+ put(ROLE, member.role.name)
+ }
+ val query = "$GROUP_ID = ? AND $PROFILE_ID = ?"
+ val args = arrayOf(member.groupId, member.profileId)
+
+ writableDatabase.insertOrUpdate(TABLE_NAME, values, query, args)
+ writableDatabase.setTransactionSuccessful()
+ } finally {
+ writableDatabase.endTransaction()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt
index 97cfc3e6e8..0e3d6f19db 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt
@@ -12,7 +12,7 @@ import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.PublicKeyValidation
import org.session.libsignal.utilities.Snode
-import org.session.libsignal.utilities.removing05PrefixIfNeeded
+import org.session.libsignal.utilities.removingIdPrefixIfNeeded
import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
@@ -127,6 +127,21 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
"""
const val INSERT_RECEIVED_HASHES_DATA = "INSERT OR IGNORE INTO $receivedMessageHashValuesTable($publicKey, $receivedMessageHashValues) SELECT $publicKey, $receivedMessageHashValues FROM $legacyReceivedMessageHashValuesTable3;"
const val DROP_LEGACY_RECEIVED_HASHES = "DROP TABLE $legacyReceivedMessageHashValuesTable3;"
+ // Open group server capabilities
+ private val serverCapabilitiesTable = "open_group_server_capabilities"
+ private val capabilities = "capabilities"
+ @JvmStatic
+ val createServerCapabilitiesCommand = "CREATE TABLE $serverCapabilitiesTable($server STRING PRIMARY KEY, $capabilities STRING)"
+ // Last inbox message server IDs
+ private val lastInboxMessageServerIdTable = "open_group_last_inbox_message_server_id_cache"
+ private val lastInboxMessageServerId = "last_inbox_message_server_id"
+ @JvmStatic
+ val createLastInboxMessageServerIdCommand = "CREATE TABLE $lastInboxMessageServerIdTable($server STRING PRIMARY KEY, $lastInboxMessageServerId INTEGER DEFAULT 0)"
+ // Last outbox message server IDs
+ private val lastOutboxMessageServerIdTable = "open_group_last_outbox_message_server_id_cache"
+ private val lastOutboxMessageServerId = "last_outbox_message_server_id"
+ @JvmStatic
+ val createLastOutboxMessageServerIdCommand = "CREATE TABLE $lastOutboxMessageServerIdTable($server STRING PRIMARY KEY, $lastOutboxMessageServerId INTEGER DEFAULT 0)"
// region Deprecated
private val deviceLinkCache = "loki_pairing_authorisation_cache"
@@ -423,14 +438,14 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
override fun getUserX25519KeyPair(): ECKeyPair {
val keyPair = IdentityKeyUtil.getIdentityKeyPair(context)
- return ECKeyPair(DjbECPublicKey(keyPair.publicKey.serialize().removing05PrefixIfNeeded()), DjbECPrivateKey(keyPair.privateKey.serialize()))
+ return ECKeyPair(DjbECPublicKey(keyPair.publicKey.serialize().removingIdPrefixIfNeeded()), DjbECPrivateKey(keyPair.privateKey.serialize()))
}
fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String) {
val database = databaseHelper.writableDatabase
val timestamp = Date().time.toString()
val index = "$groupPublicKey-$timestamp"
- val encryptionKeyPairPublicKey = encryptionKeyPair.publicKey.serialize().toHexString().removing05PrefixIfNeeded()
+ val encryptionKeyPairPublicKey = encryptionKeyPair.publicKey.serialize().toHexString().removingIdPrefixIfNeeded()
val encryptionKeyPairPrivateKey = encryptionKeyPair.privateKey.serialize().toHexString()
val row = wrap(mapOf(closedGroupsEncryptionKeyPairIndex to index, Companion.encryptionKeyPairPublicKey to encryptionKeyPairPublicKey,
Companion.encryptionKeyPairPrivateKey to encryptionKeyPairPrivateKey ))
@@ -481,6 +496,53 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
database.delete(closedGroupPublicKeysTable, "${Companion.groupPublicKey} = ?", wrap(groupPublicKey))
}
+ fun setServerCapabilities(serverName: String, serverCapabilities: List) {
+ val database = databaseHelper.writableDatabase
+ val row = wrap(mapOf(server to serverName, capabilities to serverCapabilities.joinToString(",")))
+ database.insertOrUpdate(serverCapabilitiesTable, row, "$server = ?", wrap(serverName))
+ }
+
+ fun getServerCapabilities(serverName: String): List {
+ val database = databaseHelper.writableDatabase
+ return database.get(serverCapabilitiesTable, "$server = ?", wrap(serverName)) { cursor ->
+ cursor.getString(capabilities)
+ }?.split(",") ?: emptyList()
+ }
+
+ fun setLastInboxMessageId(serverName: String, newValue: Long) {
+ val database = databaseHelper.writableDatabase
+ val row = wrap(mapOf(server to serverName, lastInboxMessageServerId to newValue.toString()))
+ database.insertOrUpdate(lastInboxMessageServerIdTable, row, "$server = ?", wrap(serverName))
+ }
+
+ fun getLastInboxMessageId(serverName: String): Long? {
+ val database = databaseHelper.writableDatabase
+ return database.get(lastInboxMessageServerIdTable, "$server = ?", wrap(serverName)) { cursor ->
+ cursor.getInt(lastInboxMessageServerId)
+ }?.toLong()
+ }
+
+ fun removeLastInboxMessageId(serverName: String) {
+ databaseHelper.writableDatabase.delete(lastInboxMessageServerIdTable, "$server = ?", wrap(serverName))
+ }
+
+ fun setLastOutboxMessageId(serverName: String, newValue: Long) {
+ val database = databaseHelper.writableDatabase
+ val row = wrap(mapOf(server to serverName, lastOutboxMessageServerId to newValue.toString()))
+ database.insertOrUpdate(lastOutboxMessageServerIdTable, row, "$server = ?", wrap(serverName))
+ }
+
+ fun getLastOutboxMessageId(serverName: String): Long? {
+ val database = databaseHelper.writableDatabase
+ return database.get(lastOutboxMessageServerIdTable, "$server = ?", wrap(serverName)) { cursor ->
+ cursor.getInt(lastOutboxMessageServerId)
+ }?.toLong()
+ }
+
+ fun removeLastOutboxMessageId(serverName: String) {
+ databaseHelper.writableDatabase.delete(lastOutboxMessageServerIdTable, "$server = ?", wrap(serverName))
+ }
+
override fun getForkInfo(): ForkInfo {
val database = databaseHelper.readableDatabase
val queryCursor = database.query(FORK_INFO_TABLE, arrayOf(HF_VALUE, SF_VALUE), "$DUMMY_KEY = $DUMMY_VALUE", null, null, null, null)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt
index 16c4750818..300217faba 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt
@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.database
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
-import org.session.libsession.messaging.open_groups.OpenGroupV2
+import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.JsonUtil
@@ -30,16 +30,16 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
return DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient)
}
- fun getAllV2OpenGroups(): Map {
+ fun getAllOpenGroups(): Map {
val database = databaseHelper.readableDatabase
var cursor: Cursor? = null
- val result = mutableMapOf()
+ val result = mutableMapOf()
try {
cursor = database.rawQuery("select * from $publicChatTable", null)
while (cursor != null && cursor.moveToNext()) {
val threadID = cursor.getLong(threadID)
val string = cursor.getString(publicChat)
- val openGroup = OpenGroupV2.fromJSON(string)
+ val openGroup = OpenGroup.fromJSON(string)
if (openGroup != null) result[threadID] = openGroup
}
} catch (e: Exception) {
@@ -50,25 +50,25 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
return result
}
- fun getOpenGroupChat(threadID: Long): OpenGroupV2? {
+ fun getOpenGroupChat(threadID: Long): OpenGroup? {
if (threadID < 0) {
return null
}
val database = databaseHelper.readableDatabase
return database.get(publicChatTable, "${Companion.threadID} = ?", arrayOf(threadID.toString())) { cursor ->
val json = cursor.getString(publicChat)
- OpenGroupV2.fromJSON(json)
+ OpenGroup.fromJSON(json)
}
}
- fun setOpenGroupChat(openGroupV2: OpenGroupV2, threadID: Long) {
+ fun setOpenGroupChat(openGroup: OpenGroup, threadID: Long) {
if (threadID < 0) {
return
}
val database = databaseHelper.writableDatabase
val contentValues = ContentValues(2)
contentValues.put(Companion.threadID, threadID)
- contentValues.put(publicChat, JsonUtil.toJson(openGroupV2.toJson()))
+ contentValues.put(publicChat, JsonUtil.toJson(openGroup.toJson()))
database.insertOrUpdate(publicChatTable, contentValues, "${Companion.threadID} = ?", arrayOf(threadID.toString()))
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java
index a3906d2871..42e41a191b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java
@@ -42,6 +42,8 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
public abstract boolean deleteMessage(long messageId);
+ public abstract void updateThreadId(long fromId, long toId);
+
public void addMismatchedIdentity(long messageId, Address address, IdentityKey identityKey) {
try {
addToDocument(messageId, MISMATCHED_IDENTITIES,
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt
index fdb9d1c161..46db0fc1d3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt
@@ -1031,6 +1031,16 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
return threadDeleted
}
+ override fun updateThreadId(fromId: Long, toId: Long) {
+ val contentValues = ContentValues(1)
+ contentValues.put(THREAD_ID, toId)
+
+ val db = databaseHelper.writableDatabase
+ db.update(SmsDatabase.TABLE_NAME, contentValues, "$THREAD_ID = ?", arrayOf("$fromId"))
+ notifyConversationListeners(toId)
+ notifyConversationListListeners()
+ }
+
fun deleteThread(threadId: Long) {
deleteThreads(setOf(threadId))
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
index a0d8715fac..cf8ed7097d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
@@ -93,12 +93,11 @@ public class MmsSmsDatabase extends Database {
MmsSmsDatabase.Reader reader = readerFor(cursor);
MessageRecord messageRecord;
+ boolean isOwnNumber = Util.isOwnNumber(context, serializedAuthor);
while ((messageRecord = reader.getNext()) != null) {
- if ((Util.isOwnNumber(context, serializedAuthor) && messageRecord.isOutgoing()) ||
- (!Util.isOwnNumber(context, serializedAuthor)
- && messageRecord.getIndividualRecipient().getAddress().serialize().equals(serializedAuthor)
- ))
+ if ((isOwnNumber && messageRecord.isOutgoing()) ||
+ (!isOwnNumber && messageRecord.getIndividualRecipient().getAddress().serialize().equals(serializedAuthor)))
{
return messageRecord;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java
index 9f3284275b..9639ed0a4e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java
@@ -255,10 +255,6 @@ public class RecipientDatabase extends Database {
recipient.resolve().setApproved(approved);
}
- public void setAllApproved(List addresses) {
-
- }
-
public void setApprovedMe(@NonNull Recipient recipient, boolean approvedMe) {
ContentValues values = new ContentValues();
values.put(APPROVED_ME, approvedMe ? 1 : 0);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
index 3e8f37cc36..6fe2eda22f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
@@ -575,6 +575,17 @@ public class SmsDatabase extends MessagingDatabase {
return threadDeleted;
}
+ @Override
+ public void updateThreadId(long fromId, long toId) {
+ ContentValues contentValues = new ContentValues(1);
+ contentValues.put(MmsSmsColumns.THREAD_ID, toId);
+
+ SQLiteDatabase db = databaseHelper.getWritableDatabase();
+ db.update(TABLE_NAME, contentValues, THREAD_ID + " = ?", new String[] {fromId + ""});
+ notifyConversationListeners(toId);
+ notifyConversationListListeners();
+ }
+
private boolean isDuplicate(IncomingTextMessage message, long threadId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + ADDRESS + " = ? AND " + THREAD_ID + " = ?",
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 5762233678..88f9409838 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database
import android.content.Context
import android.net.Uri
import org.session.libsession.database.StorageProtocol
+import org.session.libsession.messaging.BlindedIdMapping
import org.session.libsession.messaging.calls.CallMessageType
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.jobs.AttachmentUploadJob
@@ -22,12 +23,15 @@ import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
import org.session.libsession.messaging.messages.visible.Attachment
import org.session.libsession.messaging.messages.visible.VisibleMessage
-import org.session.libsession.messaging.open_groups.OpenGroupV2
+import org.session.libsession.messaging.open_groups.GroupMember
+import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
+import org.session.libsession.messaging.utilities.SessionId
+import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.messaging.utilities.UpdateMessageData
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.utilities.Address
@@ -40,6 +44,7 @@ import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.crypto.ecc.ECKeyPair
import org.session.libsignal.messages.SignalServiceAttachmentPointer
import org.session.libsignal.messages.SignalServiceGroup
+import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.KeyHelper
import org.session.libsignal.utilities.guava.Optional
import org.thoughtcrime.securesms.ApplicationContext
@@ -73,7 +78,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
override fun setUserProfilePictureURL(newValue: String) {
- val ourRecipient = Address.fromSerialized(getUserPublicKey()!!).let {
+ val ourRecipient = fromSerialized(getUserPublicKey()!!).let {
Recipient.from(context, it, false)
}
TextSecurePreferences.setProfilePictureURL(context, newValue)
@@ -125,8 +130,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
runIncrement: Boolean,
runThreadUpdate: Boolean): Long? {
var messageID: Long? = null
- val senderAddress = Address.fromSerialized(message.sender!!)
+ val senderAddress = fromSerialized(message.sender!!)
val isUserSender = (message.sender!! == getUserPublicKey())
+ val isUserBlindedSender = message.threadID?.takeIf { it >= 0 }?.let { getOpenGroup(it)?.publicKey }
+ ?.let { SodiumUtilities.sessionId(getUserPublicKey()!!, message.sender!!, it) } ?: false
val group: Optional = when {
openGroupID != null -> Optional.of(SignalServiceGroup(openGroupID.toByteArray(), SignalServiceGroup.GroupType.PUBLIC_CHAT))
groupPublicKey != null -> {
@@ -138,17 +145,17 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val pointers = attachments.mapNotNull {
it.toSignalAttachment()
}
- val targetAddress = if (isUserSender && !message.syncTarget.isNullOrEmpty()) {
- Address.fromSerialized(message.syncTarget!!)
+ val targetAddress = if ((isUserSender || isUserBlindedSender) && !message.syncTarget.isNullOrEmpty()) {
+ fromSerialized(message.syncTarget!!)
} else if (group.isPresent) {
- Address.fromSerialized(GroupUtil.getEncodedId(group.get()))
+ fromSerialized(GroupUtil.getEncodedId(group.get()))
} else {
senderAddress
}
val targetRecipient = Recipient.from(context, targetAddress, false)
if (!targetRecipient.isGroupRecipient) {
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
- if (isUserSender) {
+ if (isUserSender || isUserBlindedSender) {
recipientDb.setApproved(targetRecipient, true)
} else {
recipientDb.setApprovedMe(targetRecipient, true)
@@ -158,7 +165,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val quote: Optional = if (quotes != null) Optional.of(quotes) else Optional.absent()
val linkPreviews: Optional> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! })
val mmsDatabase = DatabaseComponent.get(context).mmsDatabase()
- val insertResult = if (message.sender == getUserPublicKey()) {
+ val insertResult = if (isUserSender || isUserBlindedSender) {
val mediaMessage = OutgoingMediaMessage.from(message, targetRecipient, pointers, quote.orNull(), linkPreviews.orNull()?.firstOrNull())
mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!, runThreadUpdate)
} else {
@@ -176,7 +183,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val smsDatabase = DatabaseComponent.get(context).smsDatabase()
val isOpenGroupInvitation = (message.openGroupInvitation != null)
- val insertResult = if (message.sender == getUserPublicKey()) {
+ val insertResult = if (isUserSender || isUserBlindedSender) {
val textMessage = if (isOpenGroupInvitation) OutgoingTextMessage.fromOpenGroupInvitation(message.openGroupInvitation, targetRecipient, message.sentTimestamp)
else OutgoingTextMessage.from(message, targetRecipient)
smsDatabase.insertMessageOutbox(message.threadID ?: -1, textMessage, message.sentTimestamp!!, runThreadUpdate)
@@ -259,12 +266,12 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseComponent.get(context).lokiAPIDatabase().setAuthToken(id, null)
}
- override fun getV2OpenGroup(threadId: Long): OpenGroupV2? {
+ override fun getOpenGroup(threadId: Long): OpenGroup? {
if (threadId.toInt() < 0) { return null }
val database = databaseHelper.readableDatabase
return database.get(LokiThreadDatabase.publicChatTable, "${LokiThreadDatabase.threadID} = ?", arrayOf( threadId.toString() )) { cursor ->
val publicChatAsJson = cursor.getString(LokiThreadDatabase.publicChat)
- OpenGroupV2.fromJSON(publicChatAsJson)
+ OpenGroup.fromJSON(publicChatAsJson)
}
}
@@ -309,6 +316,14 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseComponent.get(context).lokiMessageDatabase().setOriginalThreadID(messageID, serverID, threadID)
}
+ override fun getOpenGroup(room: String, server: String): OpenGroup? {
+ return getAllOpenGroups().values.firstOrNull { it.server == server && it.room == room }
+ }
+
+ override fun addGroupMember(member: GroupMember) {
+ DatabaseComponent.get(context).groupMemberDatabase().addGroupMember(member)
+ }
+
override fun isDuplicateMessage(timestamp: Long): Boolean {
return getReceivedMessageTimestamps().contains(timestamp)
}
@@ -335,7 +350,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun getMessageIdInDatabase(timestamp: Long, author: String): Long? {
val database = DatabaseComponent.get(context).mmsSmsDatabase()
- val address = Address.fromSerialized(author)
+ val address = fromSerialized(author)
return database.getMessageFor(timestamp, address)?.getId()
}
@@ -453,7 +468,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, sentTimestamp: Long) {
val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList())
- val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
+ val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON()
val infoMessage = IncomingGroupMessage(m, groupID, updateData, true)
val smsDB = DatabaseComponent.get(context).smsDatabase()
@@ -462,7 +477,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, threadID: Long, sentTimestamp: Long) {
val userPublicKey = getUserPublicKey()
- val recipient = Recipient.from(context, Address.fromSerialized(groupID), false)
+ val recipient = Recipient.from(context, fromSerialized(groupID), false)
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: ""
val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, true, null, listOf(), listOf())
@@ -475,7 +490,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun isClosedGroup(publicKey: String): Boolean {
val isClosedGroup = DatabaseComponent.get(context).lokiAPIDatabase().isClosedGroup(publicKey)
- val address = Address.fromSerialized(publicKey)
+ val address = fromSerialized(publicKey)
return address.isClosedGroup || isClosedGroup
}
@@ -528,8 +543,20 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseComponent.get(context).recipientDatabase().setExpireMessages(recipient, duration);
}
- override fun getAllV2OpenGroups(): Map {
- return DatabaseComponent.get(context).lokiThreadDatabase().getAllV2OpenGroups()
+ override fun setServerCapabilities(server: String, capabilities: List) {
+ return DatabaseComponent.get(context).lokiAPIDatabase().setServerCapabilities(server, capabilities)
+ }
+
+ override fun getServerCapabilities(server: String): List {
+ return DatabaseComponent.get(context).lokiAPIDatabase().getServerCapabilities(server)
+ }
+
+ override fun getAllOpenGroups(): Map {
+ return DatabaseComponent.get(context).lokiThreadDatabase().getAllOpenGroups()
+ }
+
+ override fun updateOpenGroup(openGroup: OpenGroup) {
+ OpenGroupManager.updateOpenGroup(openGroup, context)
}
override fun getAllGroups(): List {
@@ -541,7 +568,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
override fun onOpenGroupAdded(urlAsString: String) {
- val server = OpenGroupV2.getServer(urlAsString)
+ val server = OpenGroup.getServer(urlAsString)
OpenGroupManager.restartPollerForServer(server.toString().removeSuffix("/"))
}
@@ -562,20 +589,20 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): Long {
val database = DatabaseComponent.get(context).threadDatabase()
- if (!openGroupID.isNullOrEmpty()) {
- val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())), false)
- return database.getThreadIdIfExistsFor(recipient)
+ return if (!openGroupID.isNullOrEmpty()) {
+ val recipient = Recipient.from(context, fromSerialized(GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())), false)
+ database.getThreadIdIfExistsFor(recipient)
} else if (!groupPublicKey.isNullOrEmpty()) {
- val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)), false)
- return database.getOrCreateThreadIdFor(recipient)
+ val recipient = Recipient.from(context, fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)), false)
+ database.getOrCreateThreadIdFor(recipient)
} else {
- val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
- return database.getOrCreateThreadIdFor(recipient)
+ val recipient = Recipient.from(context, fromSerialized(publicKey), false)
+ database.getOrCreateThreadIdFor(recipient)
}
}
override fun getThreadId(publicKeyOrOpenGroupID: String): Long? {
- val address = Address.fromSerialized(publicKeyOrOpenGroupID)
+ val address = fromSerialized(publicKeyOrOpenGroupID)
return getThreadId(address)
}
@@ -622,8 +649,13 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun addContacts(contacts: List) {
val recipientDatabase = DatabaseComponent.get(context).recipientDatabase()
val threadDatabase = DatabaseComponent.get(context).threadDatabase()
- for (contact in contacts) {
- val address = Address.fromSerialized(contact.publicKey)
+ val mappingDb = DatabaseComponent.get(context).blindedIdMappingDatabase()
+ val moreContacts = contacts.filter { contact ->
+ val id = SessionId(contact.publicKey)
+ id.prefix != IdPrefix.BLINDED || mappingDb.getBlindedIdMapping(contact.publicKey).none { it.sessionId != null }
+ }
+ for (contact in moreContacts) {
+ val address = fromSerialized(contact.publicKey)
val recipient = Recipient.from(context, address, true)
if (!contact.profilePicture.isNullOrEmpty()) {
recipientDatabase.setProfileAvatar(recipient, contact.profilePicture)
@@ -715,12 +747,48 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
threadDB.setHasSent(threadId, true)
} else {
val mmsDb = DatabaseComponent.get(context).mmsDatabase()
- val senderAddress = fromSerialized(senderPublicKey)
- val requestSender = Recipient.from(context, senderAddress, false)
- recipientDb.setApprovedMe(requestSender, true)
+ val smsDb = DatabaseComponent.get(context).smsDatabase()
+ val sender = Recipient.from(context, fromSerialized(senderPublicKey), false)
+ val threadId = threadDB.getOrCreateThreadIdFor(sender)
+ threadDB.setHasSent(threadId, true)
+ val mappingDb = DatabaseComponent.get(context).blindedIdMappingDatabase()
+ val mappings = mutableMapOf()
+ threadDB.readerFor(threadDB.conversationList).use { reader ->
+ while (reader.next != null) {
+ val recipient = reader.current.recipient
+ val address = recipient.address.serialize()
+ val blindedId = when {
+ recipient.isGroupRecipient -> null
+ recipient.isOpenGroupInboxRecipient -> {
+ GroupUtil.getDecodedOpenGroupInbox(address)
+ }
+ else -> {
+ if (SessionId(address).prefix == IdPrefix.BLINDED) {
+ address
+ } else null
+ }
+ } ?: continue
+ mappingDb.getBlindedIdMapping(blindedId).firstOrNull()?.let {
+ mappings[address] = it
+ }
+ }
+ }
+ for (mapping in mappings) {
+ if (!SodiumUtilities.sessionId(senderPublicKey, mapping.value.blindedId, mapping.value.serverId)) {
+ continue
+ }
+ mappingDb.addBlindedIdMapping(mapping.value.copy(sessionId = senderPublicKey))
+
+ val blindedThreadId = threadDB.getOrCreateThreadIdFor(Recipient.from(context, fromSerialized(mapping.key), false))
+ mmsDb.updateThreadId(blindedThreadId, threadId)
+ smsDb.updateThreadId(blindedThreadId, threadId)
+ threadDB.deleteConversation(blindedThreadId)
+ }
+ recipientDb.setApproved(sender, true)
+ recipientDb.setApprovedMe(sender, true)
val message = IncomingMediaMessage(
- senderAddress,
+ sender.address,
response.sentTimestamp!!,
-1,
0,
@@ -735,7 +803,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
Optional.absent(),
Optional.absent()
)
- val threadId = getOrCreateThreadIdFor(senderAddress)
mmsDb.insertSecureDecryptedMessageInbox(message, threadId, runIncrement = true, runThreadUpdate = true)
}
}
@@ -748,7 +815,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseComponent.get(context).recipientDatabase().setApprovedMe(recipient, approvedMe)
}
-
override fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) {
val database = DatabaseComponent.get(context).smsDatabase()
val address = fromSerialized(senderPublicKey)
@@ -764,4 +830,62 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return database.getLastSeenAndHasSent(threadId).second() ?: false
}
+
+ override fun getLastInboxMessageId(server: String): Long? {
+ return DatabaseComponent.get(context).lokiAPIDatabase().getLastInboxMessageId(server)
+ }
+
+ override fun setLastInboxMessageId(server: String, messageId: Long) {
+ DatabaseComponent.get(context).lokiAPIDatabase().setLastInboxMessageId(server, messageId)
+ }
+
+ override fun removeLastInboxMessageId(server: String) {
+ DatabaseComponent.get(context).lokiAPIDatabase().removeLastInboxMessageId(server)
+ }
+
+ override fun getLastOutboxMessageId(server: String): Long? {
+ return DatabaseComponent.get(context).lokiAPIDatabase().getLastOutboxMessageId(server)
+ }
+
+ override fun setLastOutboxMessageId(server: String, messageId: Long) {
+ DatabaseComponent.get(context).lokiAPIDatabase().setLastOutboxMessageId(server, messageId)
+ }
+
+ override fun removeLastOutboxMessageId(server: String) {
+ DatabaseComponent.get(context).lokiAPIDatabase().removeLastOutboxMessageId(server)
+ }
+
+ override fun getOrCreateBlindedIdMapping(
+ blindedId: String,
+ server: String,
+ serverPublicKey: String,
+ fromOutbox: Boolean
+ ): BlindedIdMapping {
+ val db = DatabaseComponent.get(context).blindedIdMappingDatabase()
+ val mapping = db.getBlindedIdMapping(blindedId).firstOrNull() ?: BlindedIdMapping(blindedId, null, server, serverPublicKey)
+ if (mapping.sessionId != null) {
+ return mapping
+ }
+ val threadDb = DatabaseComponent.get(context).threadDatabase()
+ threadDb.readerFor(threadDb.conversationList).use { reader ->
+ while (reader.next != null) {
+ val recipient = reader.current.recipient
+ val sessionId = recipient.address.serialize()
+ if (!recipient.isGroupRecipient && SodiumUtilities.sessionId(sessionId, blindedId, serverPublicKey)) {
+ val contactMapping = mapping.copy(sessionId = sessionId)
+ db.addBlindedIdMapping(contactMapping)
+ return contactMapping
+ }
+ }
+ }
+ db.getBlindedIdMappingsExceptFor(server).forEach {
+ if (SodiumUtilities.sessionId(it.sessionId!!, blindedId, serverPublicKey)) {
+ val otherMapping = mapping.copy(sessionId = it.sessionId)
+ db.addBlindedIdMapping(otherMapping)
+ return otherMapping
+ }
+ }
+ db.addBlindedIdMapping(mapping)
+ return mapping
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java
index feaa70dd7f..a64051ea27 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java
@@ -43,6 +43,7 @@ import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsession.utilities.recipients.Recipient.RecipientSettings;
+import org.session.libsignal.utilities.IdPrefix;
import org.session.libsignal.utilities.Log;
import org.session.libsignal.utilities.Pair;
import org.session.libsignal.utilities.guava.Optional;
@@ -447,6 +448,11 @@ public class ThreadDatabase extends Database {
return getConversationList(where);
}
+ public Cursor getBlindedConversationList() {
+ String where = TABLE_NAME + "." + ADDRESS + " LIKE '" + IdPrefix.BLINDED.getValue() + "%' ";
+ return getConversationList(where);
+ }
+
public Cursor getApprovedConversationList() {
String where = "((" + MESSAGE_COUNT + " != 0 AND (" + HAS_SENT + " = 1 OR " + RecipientDatabase.APPROVED + " = 1 OR "+ GroupDatabase.TABLE_NAME +"."+GROUP_ID+" LIKE '"+CLOSED_GROUP_PREFIX+"%')) OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + OPEN_GROUP_PREFIX + "%') " +
"AND " + ARCHIVED + " = 0 ";
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
index 460b346b7e..0cf96119db 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
@@ -14,8 +14,10 @@ import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.utilities.Log;
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
+import org.thoughtcrime.securesms.database.BlindedIdMappingDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase;
+import org.thoughtcrime.securesms.database.GroupMemberDatabase;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
import org.thoughtcrime.securesms.database.JobDatabase;
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
@@ -67,9 +69,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV32 = 53;
private static final int lokiV33 = 54;
private static final int lokiV34 = 55;
+ private static final int lokiV35 = 55;
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
- private static final int DATABASE_VERSION = lokiV34;
+ private static final int DATABASE_VERSION = lokiV35;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@@ -135,6 +138,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiAPIDatabase.getCreateOpenGroupProfilePictureTableCommand());
db.execSQL(LokiAPIDatabase.getCreateClosedGroupEncryptionKeyPairsTable());
db.execSQL(LokiAPIDatabase.getCreateClosedGroupPublicKeysTable());
+ db.execSQL(LokiAPIDatabase.getCreateServerCapabilitiesCommand());
+ db.execSQL(LokiAPIDatabase.getCreateLastInboxMessageServerIdCommand());
+ db.execSQL(LokiAPIDatabase.getCreateLastOutboxMessageServerIdCommand());
db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand());
db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand());
db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand());
@@ -161,6 +167,8 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiAPIDatabase.DROP_LEGACY_LAST_HASH);
db.execSQL(LokiAPIDatabase.INSERT_RECEIVED_HASHES_DATA);
db.execSQL(LokiAPIDatabase.DROP_LEGACY_RECEIVED_HASHES);
+ db.execSQL(BlindedIdMappingDatabase.CREATE_BLINDED_ID_MAPPING_TABLE_COMMAND);
+ db.execSQL(GroupMemberDatabase.CREATE_GROUP_MEMBER_TABLE_COMMAND);
executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS);
@@ -369,6 +377,14 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiAPIDatabase.DROP_LEGACY_RECEIVED_HASHES);
}
+ if (oldVersion < lokiV35) {
+ db.execSQL(LokiAPIDatabase.getCreateServerCapabilitiesCommand());
+ db.execSQL(LokiAPIDatabase.getCreateLastInboxMessageServerIdCommand());
+ db.execSQL(LokiAPIDatabase.getCreateLastOutboxMessageServerIdCommand());
+ db.execSQL(BlindedIdMappingDatabase.CREATE_BLINDED_ID_MAPPING_TABLE_COMMAND);
+ db.execSQL(GroupMemberDatabase.CREATE_GROUP_MEMBER_TABLE_COMMAND);
+ }
+
db.setTransactionSuccessful();
} finally {
db.endTransaction();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java
index 03022b69cc..372f142f99 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java
@@ -115,6 +115,8 @@ public class ThreadRecord extends DisplayRecord {
return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_verified));
} else if (SmsDatabase.Types.isIdentityDefault(type)) {
return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_unverified));
+ } else if (MmsSmsColumns.Types.isMessageRequestResponse(type)) {
+ return emphasisAdded(context.getString(R.string.message_requests_accepted));
} else if (getCount() == 0) {
return new SpannableString(context.getString(R.string.ThreadRecord_empty_message));
} else {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt
index 382b0f4b74..4b1d346cbf 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt
@@ -42,4 +42,6 @@ interface DatabaseComponent {
fun sessionContactDatabase(): SessionContactDatabase
fun storage(): Storage
fun attachmentProvider(): MessageDataProvider
+ fun blindedIdMappingDatabase(): BlindedIdMappingDatabase
+ fun groupMemberDatabase(): GroupMemberDatabase
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt
index 0a1171e504..a6afdc75f3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt
@@ -117,6 +117,14 @@ object DatabaseModule {
@Singleton
fun provideSessionContactDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = SessionContactDatabase(context,openHelper)
+ @Provides
+ @Singleton
+ fun provideBlindedIdMappingDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = BlindedIdMappingDatabase(context, openHelper)
+
+ @Provides
+ @Singleton
+ fun provideGroupMemberDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = GroupMemberDatabase(context, openHelper)
+
@Provides
@Singleton
fun provideStorage(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = Storage(context,openHelper)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/DefaultGroupsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/DefaultGroupsViewModel.kt
index a9b6662d8b..c93b1e6026 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/DefaultGroupsViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/DefaultGroupsViewModel.kt
@@ -4,19 +4,22 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
+import nl.komponents.kovenant.functional.map
+import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.thoughtcrime.securesms.util.State
-typealias DefaultGroups = List
+typealias DefaultGroups = List
typealias GroupState = State
class DefaultGroupsViewModel : ViewModel() {
init {
- OpenGroupAPIV2.getDefaultRoomsIfNeeded()
+ OpenGroupApi.getDefaultServerCapabilities().map {
+ OpenGroupApi.getDefaultRoomsIfNeeded()
+ }
}
- val defaultRooms = OpenGroupAPIV2.defaultRooms.map {
+ val defaultRooms = OpenGroupApi.defaultRooms.map {
State.Success(it)
}.onStart {
emit(State.Loading)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinPublicChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinPublicChatActivity.kt
index e464d69a0e..c2d37ba2e9 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinPublicChatActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinPublicChatActivity.kt
@@ -26,7 +26,7 @@ import network.loki.messenger.databinding.ActivityJoinPublicChatBinding
import network.loki.messenger.databinding.FragmentEnterChatUrlBinding
import okhttp3.HttpUrl
import org.session.libsession.messaging.MessagingModuleConfiguration
-import org.session.libsession.messaging.open_groups.OpenGroupAPIV2.DefaultGroup
+import org.session.libsession.messaging.open_groups.OpenGroupApi.DefaultGroup
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.recipients.Recipient
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt
index 90f287cb1c..82ba43ab15 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt
@@ -4,16 +4,17 @@ import android.content.Context
import androidx.annotation.WorkerThread
import okhttp3.HttpUrl
import org.session.libsession.messaging.MessagingModuleConfiguration
-import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
-import org.session.libsession.messaging.open_groups.OpenGroupV2
-import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPollerV2
+import org.session.libsession.messaging.open_groups.GroupMemberRole
+import org.session.libsession.messaging.open_groups.OpenGroup
+import org.session.libsession.messaging.open_groups.OpenGroupApi
+import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller
import org.session.libsignal.utilities.ThreadUtils
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import java.util.concurrent.Executors
object OpenGroupManager {
private val executorService = Executors.newScheduledThreadPool(4)
- private var pollers = mutableMapOf() // One for each server
+ private var pollers = mutableMapOf() // One for each server
private var isPolling = false
private val pollUpdaterLock = Any()
@@ -38,10 +39,10 @@ object OpenGroupManager {
if (isPolling) { return }
isPolling = true
val storage = MessagingModuleConfiguration.shared.storage
- val servers = storage.getAllV2OpenGroups().values.map { it.server }.toSet()
+ val servers = storage.getAllOpenGroups().values.map { it.server }.toSet()
servers.forEach { server ->
pollers[server]?.stop() // Shouldn't be necessary
- val poller = OpenGroupPollerV2(server, executorService)
+ val poller = OpenGroupPoller(server, executorService)
poller.startIfNeeded()
pollers[server] = poller
}
@@ -67,17 +68,21 @@ object OpenGroupManager {
// Clear any existing data if needed
storage.removeLastDeletionServerID(room, server)
storage.removeLastMessageServerID(room, server)
+ storage.removeLastInboxMessageId(server)
+ storage.removeLastOutboxMessageId(server)
// Store the public key
storage.setOpenGroupPublicKey(server,publicKey)
- // Get group info
- OpenGroupAPIV2.getAuthToken(room, server).get()
- // Get group info
- val info = OpenGroupAPIV2.getInfo(room, server).get()
+ // Get capabilities
+ val capabilities = OpenGroupApi.getCapabilities(server).get()
+ storage.setServerCapabilities(server, capabilities.capabilities)
+ // Get room info
+ val info = OpenGroupApi.getRoomInfo(room, server).get()
+ storage.setUserCount(room, server, info.activeUsers)
// Create the group locally if not available already
if (threadID < 0) {
threadID = GroupManager.createOpenGroup(openGroupID, context, null, info.name).threadId
}
- val openGroup = OpenGroupV2(server, room, info.name, publicKey)
+ val openGroup = OpenGroup(server, room, info.name, info.infoUpdates, publicKey)
threadDB.setOpenGroupChat(openGroup, threadID)
}
@@ -86,7 +91,7 @@ object OpenGroupManager {
synchronized(pollUpdaterLock) {
pollers[server]?.stop()
pollers[server]?.startIfNeeded() ?: run {
- val poller = OpenGroupPollerV2(server, executorService)
+ val poller = OpenGroupPoller(server, executorService)
pollers[server] = poller
poller.startIfNeeded()
}
@@ -102,7 +107,7 @@ object OpenGroupManager {
threadDB.setThreadArchived(threadID)
val groupID = recipient.address.serialize()
// Stop the poller if needed
- val openGroups = storage.getAllV2OpenGroups().filter { it.value.server == server }
+ val openGroups = storage.getAllOpenGroups().filter { it.value.server == server }
if (openGroups.count() == 1) {
synchronized(pollUpdaterLock) {
val poller = pollers[server]
@@ -113,6 +118,8 @@ object OpenGroupManager {
// Delete
storage.removeLastDeletionServerID(room, server)
storage.removeLastMessageServerID(room, server)
+ storage.removeLastInboxMessageId(server)
+ storage.removeLastOutboxMessageId(server)
val lokiThreadDB = DatabaseComponent.get(context).lokiThreadDatabase()
lokiThreadDB.removeOpenGroupChat(threadID)
ThreadUtils.queue {
@@ -123,9 +130,26 @@ object OpenGroupManager {
fun addOpenGroup(urlAsString: String, context: Context) {
val url = HttpUrl.parse(urlAsString) ?: return
- val server = OpenGroupV2.getServer(urlAsString)
+ val server = OpenGroup.getServer(urlAsString)
val room = url.pathSegments().firstOrNull() ?: return
val publicKey = url.queryParameter("public_key") ?: return
add(server.toString().removeSuffix("/"), room, publicKey, context)
}
+
+ fun updateOpenGroup(openGroup: OpenGroup, context: Context) {
+ val threadDB = DatabaseComponent.get(context).lokiThreadDatabase()
+ val openGroupID = "${openGroup.server}.${openGroup.room}"
+ val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context)
+ threadDB.setOpenGroupChat(openGroup, threadID)
+ }
+
+ fun isUserModerator(context: Context, groupId: String, standardPublicKey: String, blindedPublicKey: String? = null): Boolean {
+ val memberDatabase = DatabaseComponent.get(context).groupMemberDatabase()
+ val standardRoles = memberDatabase.getGroupMemberRoles(groupId, standardPublicKey)
+ val blindedRoles = blindedPublicKey?.let { memberDatabase.getGroupMemberRoles(groupId, it) } ?: emptyList()
+
+ return GroupMemberRole.ADMIN in standardRoles || GroupMemberRole.MODERATOR in standardRoles ||
+ GroupMemberRole.ADMIN in blindedRoles || GroupMemberRole.MODERATOR in blindedRoles
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupUtilities.kt
index ea4dc038a7..f2147aaf37 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupUtilities.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupUtilities.kt
@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.groups
import android.content.Context
import androidx.annotation.WorkerThread
import org.greenrobot.eventbus.EventBus
-import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
+import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
@@ -29,8 +29,7 @@ object OpenGroupUtilities {
throw IllegalStateException("Attempt to update open group info for non-existent DB record: $groupId")
}
- val info = OpenGroupAPIV2.getInfo(room, server).get() // store info again?
- OpenGroupAPIV2.getMemberCount(room, server).get()
+ val info = OpenGroupApi.getRoomInfo(room, server).get() // store info again?
EventBus.getDefault().post(GroupInfoUpdatedEvent(server, room = room))
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt
index 261890ba59..433d17f675 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt
@@ -82,7 +82,7 @@ class ConversationView : LinearLayout {
}
binding.muteIndicatorImageView.setImageResource(drawableRes)
val rawSnippet = thread.getDisplayBody(context)
- val snippet = highlightMentions(rawSnippet, recipient.isOpenGroupRecipient, context)
+ val snippet = highlightMentions(rawSnippet, thread.threadId, context)
binding.snippetTextView.text = snippet
binding.snippetTextView.typeface = if (unreadCount > 0 && !thread.isRead) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
binding.snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE
diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt
index 3f7e997b54..37e4a90dd8 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt
@@ -21,6 +21,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.recipients.Recipient
+import org.session.libsignal.utilities.IdPrefix
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
@@ -83,8 +84,8 @@ class UserDetailsBottomSheet : BottomSheetDialogFragment() {
}
nameTextView.text = recipient.name ?: publicKey // Uses the Contact API internally
- publicKeyTextView.isVisible = !threadRecipient.isOpenGroupRecipient
- messageButton.isVisible = !threadRecipient.isOpenGroupRecipient
+ publicKeyTextView.isVisible = !threadRecipient.isOpenGroupRecipient && !threadRecipient.isOpenGroupInboxRecipient
+ messageButton.isVisible = !threadRecipient.isOpenGroupRecipient || IdPrefix.fromValue(publicKey) == IdPrefix.BLINDED
publicKeyTextView.text = publicKey
publicKeyTextView.setOnLongClickListener {
val clipboard =
@@ -103,6 +104,7 @@ class UserDetailsBottomSheet : BottomSheetDialogFragment() {
)
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId ?: -1)
+ intent.putExtra(ConversationActivityV2.FROM_GROUP_THREAD_ID, threadID)
startActivity(intent)
dismiss()
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java b/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java
index f71050aba9..179c28bc3c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java
@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.mms;
import android.content.Context;
-import org.session.libsession.messaging.file_server.FileServerAPIV2;
+import org.session.libsession.messaging.file_server.FileServerApi;
public class PushMediaConstraints extends MediaConstraints {
@@ -21,26 +21,26 @@ public class PushMediaConstraints extends MediaConstraints {
@Override
public int getImageMaxSize(Context context) {
- return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
+ return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
}
@Override
public int getGifMaxSize(Context context) {
- return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
+ return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
}
@Override
public int getVideoMaxSize(Context context) {
- return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
+ return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
}
@Override
public int getAudioMaxSize(Context context) {
- return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
+ return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
}
@Override
public int getDocumentMaxSize(Context context) {
- return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier);
+ return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier);
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt
index 95760a093d..2405a305f4 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt
@@ -17,7 +17,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
import org.session.libsession.messaging.jobs.MessageReceiveParameters
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2
-import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPollerV2
+import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.Log
@@ -72,13 +72,13 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
// Open Groups
val threadDB = DatabaseComponent.get(context).lokiThreadDatabase()
- val v2OpenGroups = threadDB.getAllV2OpenGroups()
- val v2OpenGroupServers = v2OpenGroups.map { it.value.server }.toSet()
+ val openGroups = threadDB.getAllOpenGroups()
+ val openGroupServers = openGroups.map { it.value.server }.toSet()
- for (server in v2OpenGroupServers) {
- val poller = OpenGroupPollerV2(server, null)
+ for (server in openGroupServers) {
+ val poller = OpenGroupPoller(server, null)
poller.hasStarted = true
- promises.add(poller.poll(true))
+ promises.add(poller.poll())
}
// Wait until all the promises are resolved
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 63709a0e9e..e0645a588c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java
@@ -36,15 +36,22 @@ import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
+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.utilities.Address;
import org.session.libsession.utilities.Contact;
import org.session.libsession.utilities.ServiceUtil;
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.Util;
import org.thoughtcrime.securesms.ApplicationContext;
@@ -52,6 +59,8 @@ import org.thoughtcrime.securesms.contactshare.ContactUtil;
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities;
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities;
+import org.thoughtcrime.securesms.crypto.KeyPairUtilities;
+import org.thoughtcrime.securesms.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
@@ -66,9 +75,11 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.SessionMetaProtocol;
import org.thoughtcrime.securesms.util.SpanUtil;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -491,6 +502,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
MmsSmsDatabase.Reader reader = DatabaseComponent.get(context).mmsSmsDatabase().readerFor(cursor);
ThreadDatabase threadDatabase = DatabaseComponent.get(context).threadDatabase();
MessageRecord record;
+ Map cache = new HashMap();
while ((record = reader.getNext()) != null) {
long id = record.getId();
@@ -534,16 +546,22 @@ public class DefaultMessageNotifier implements MessageNotifier {
if (threadRecipients == null || !threadRecipients.isMuted()) {
if (threadRecipients != null && threadRecipients.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS) {
+ String userPublicKey = TextSecurePreferences.getLocalNumber(context);
+ String blindedPublicKey = cache.get(threadId);
+ if (blindedPublicKey == null) {
+ blindedPublicKey = generateBlindedId(threadId, context);
+ cache.put(threadId, blindedPublicKey);
+ }
// check if mentioned here
boolean isQuoteMentioned = false;
if (record instanceof MmsMessageRecord) {
Quote quote = ((MmsMessageRecord) record).getQuote();
Address quoteAddress = quote != null ? quote.getAuthor() : null;
String serializedAddress = quoteAddress != null ? quoteAddress.serialize() : null;
- isQuoteMentioned = serializedAddress != null && Objects.equals(TextSecurePreferences.getLocalNumber(context), serializedAddress);
+ isQuoteMentioned = (serializedAddress!= null && Objects.equals(userPublicKey, serializedAddress)) ||
+ (blindedPublicKey != null && Objects.equals(userPublicKey, blindedPublicKey));
}
- if (body.toString().contains("@"+TextSecurePreferences.getLocalNumber(context))
- || isQuoteMentioned) {
+ if (body.toString().contains("@"+userPublicKey) || body.toString().contains("@"+blindedPublicKey) || isQuoteMentioned) {
notificationState.addNotification(new NotificationItem(id, mms, recipient, conversationRecipient, threadRecipients, threadId, body, timestamp, slideDeck));
}
} else if (threadRecipients != null && threadRecipients.notifyType == RecipientDatabase.NOTIFY_TYPE_NONE) {
@@ -558,6 +576,19 @@ public class DefaultMessageNotifier implements MessageNotifier {
return notificationState;
}
+ private @Nullable String generateBlindedId(long threadId, Context context) {
+ LokiThreadDatabase lokiThreadDatabase = DatabaseComponent.get(context).lokiThreadDatabase();
+ OpenGroup openGroup = lokiThreadDatabase.getOpenGroupChat(threadId);
+ KeyPair edKeyPair = KeyPairUtilities.INSTANCE.getUserED25519KeyPair(context);
+ if (openGroup != null && edKeyPair != null) {
+ KeyPair blindedKeyPair = SodiumUtilities.INSTANCE.blindedKeyPair(openGroup.getPublicKey(), edKeyPair);
+ if (blindedKeyPair != null) {
+ return new SessionId(IdPrefix.BLINDED, blindedKeyPair.getPublicKey().getAsBytes()).getHexString();
+ }
+ }
+ return null;
+ }
+
private void updateBadge(Context context, int count) {
try {
if (count == 0) ShortcutBadger.removeCount(context);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt
index 48a649dee3..adaec0e17a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt
@@ -1,12 +1,14 @@
package org.thoughtcrime.securesms.notifications
import android.content.Context
+import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.map
import okhttp3.MediaType
import okhttp3.Request
import okhttp3.RequestBody
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
import org.session.libsession.snode.OnionRequestAPI
+import org.session.libsession.snode.Version
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.utilities.Log
@@ -43,7 +45,7 @@ object LokiPushNotificationManager {
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
val request = Request.Builder().url(url).post(body)
retryIfNeeded(maxRetryCount) {
- OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, "/loki/v2/lsrpc").map { json ->
+ getResponseBody(request.build()).map { json ->
val code = json["code"] as? Int
if (code != null && code != 0) {
TextSecurePreferences.setIsUsingFCM(context, false)
@@ -72,7 +74,7 @@ object LokiPushNotificationManager {
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
val request = Request.Builder().url(url).post(body)
retryIfNeeded(maxRetryCount) {
- OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, "/loki/v2/lsrpc").map { json ->
+ getResponseBody(request.build()).map { json ->
val code = json["code"] as? Int
if (code != null && code != 0) {
TextSecurePreferences.setIsUsingFCM(context, true)
@@ -100,7 +102,7 @@ object LokiPushNotificationManager {
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
val request = Request.Builder().url(url).post(body)
retryIfNeeded(maxRetryCount) {
- OnionRequestAPI.sendOnionRequest(request.build(), server, pnServerPublicKey, "/loki/v2/lsrpc").map { json ->
+ getResponseBody(request.build()).map { json ->
val code = json["code"] as? Int
if (code == null || code == 0) {
Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${json["message"] as? String ?: "null"}.")
@@ -110,4 +112,10 @@ object LokiPushNotificationManager {
}
}
}
+
+ private fun getResponseBody(request: Request): Promise