diff --git a/app/build.gradle b/app/build.gradle index 5016a1fded..25379895eb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,8 +31,8 @@ configurations.all { exclude module: "commons-logging" } -def canonicalVersionCode = 371 -def canonicalVersionName = "1.18.2" +def canonicalVersionCode = 372 +def canonicalVersionName = "1.18.3" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 5a8b03fdf8..03b56d6b61 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -57,6 +57,7 @@ import org.signal.aesgcmprovider.AesGcmProvider; import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.crypto.KeyPairUtilities; import org.thoughtcrime.securesms.database.EmojiSearchDatabase; +import org.thoughtcrime.securesms.database.LastSentTimestampCache; import org.thoughtcrime.securesms.database.LokiAPIDatabase; import org.thoughtcrime.securesms.database.Storage; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; @@ -149,6 +150,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO @Inject TextSecurePreferences textSecurePreferences; @Inject PushRegistry pushRegistry; @Inject ConfigFactory configFactory; + @Inject LastSentTimestampCache lastSentTimestampCache; CallMessageProcessor callMessageProcessor; MessagingModuleConfiguration messagingModuleConfiguration; @@ -218,7 +220,8 @@ public class ApplicationContext extends Application implements DefaultLifecycleO device, messageDataProvider, ()-> KeyPairUtilities.INSTANCE.getUserED25519KeyPair(this), - configFactory + configFactory, + lastSentTimestampCache ); callMessageProcessor = new CallMessageProcessor(this, textSecurePreferences, ProcessLifecycleOwner.get().getLifecycle(), storage); Log.i(TAG, "onCreate()"); diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt index 20dc2bbade..6445abed3b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -5,6 +5,8 @@ import android.text.TextUtils import com.google.protobuf.ByteString import org.greenrobot.eventbus.EventBus import org.session.libsession.database.MessageDataProvider +import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState @@ -184,10 +186,15 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) override fun deleteMessage(messageID: Long, isSms: Boolean) { val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase() else DatabaseComponent.get(context).mmsDatabase() + val (threadId, timestamp) = runCatching { messagingDatabase.getMessageRecord(messageID).run { threadId to timestamp } }.getOrNull() ?: (null to null) messagingDatabase.deleteMessage(messageID) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessage(messageID, isSms) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHash(messageID, mms = !isSms) + + threadId ?: return + timestamp ?: return + MessagingModuleConfiguration.shared.lastSentTimestampCache.delete(threadId, timestamp) } override fun deleteMessages(messageIDs: List, threadId: Long, isSms: Boolean) { @@ -195,12 +202,17 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase() else DatabaseComponent.get(context).mmsDatabase() + val messages = messageIDs.mapNotNull { runCatching { messagingDatabase.getMessageRecord(it) }.getOrNull() } + // Perform local delete messagingDatabase.deleteMessages(messageIDs.toLongArray(), threadId) // Perform online delete DatabaseComponent.get(context).lokiMessageDatabase().deleteMessages(messageIDs) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHashes(messageIDs, mms = !isSms) + + val threadId = messages.firstOrNull()?.threadId + threadId?.let{ MessagingModuleConfiguration.shared.lastSentTimestampCache.delete(it, messages.map { it.timestamp }) } } override fun updateMessageAsDeleted(timestamp: Long, author: String): Long? { 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 1f075e99dc..76bf7b875f 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 @@ -327,7 +327,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } }, onAttachmentNeedsDownload = { attachmentId, mmsId -> - // Start download (on IO thread) lifecycleScope.launch(Dispatchers.IO) { JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) } @@ -337,8 +336,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe ) adapter.visibleMessageViewDelegate = this - // Register an AdapterDataObserver to scroll us to the bottom of the RecyclerView if we're - // already near the the bottom and the data changes. + // Register an AdapterDataObserver to scroll us to the bottom of the RecyclerView for if + // we're already near the the bottom and the data changes. adapter.registerAdapterDataObserver(ConversationAdapterDataObserver(binding?.conversationRecyclerView!!, adapter)) adapter @@ -376,7 +375,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe const val PICK_GIF = 10 const val PICK_FROM_LIBRARY = 12 const val INVITE_CONTACTS = 124 - } // endregion @@ -574,7 +572,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe binding!!.conversationRecyclerView.layoutManager = layoutManager // Workaround for the fact that CursorRecyclerViewAdapter doesn't auto-update automatically (even though it says it will) LoaderManager.getInstance(this).restartLoader(0, null, this) - binding!!.conversationRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + binding!!.conversationRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { // The unreadCount check is to prevent us scrolling to the bottom when we first enter a conversation @@ -1888,7 +1886,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe Log.w("ConversationActivityV2", "Asked to delete messages but could not obtain viewModel recipient - aborting.") return } - + val allSentByCurrentUser = messages.all { it.isOutgoing } val allHasHash = messages.all { lokiMessageDb.getMessageServerHash(it.id, it.isMms) != null } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationLoader.kt index 4692bf7862..2ac613bf66 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationLoader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationLoader.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2 import android.content.Context import android.database.Cursor +import org.session.libsession.messaging.MessagingModuleConfiguration import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.util.AbstractCursorLoader @@ -12,6 +13,7 @@ class ConversationLoader( ) : AbstractCursorLoader(context) { override fun getCursor(): Cursor { + MessagingModuleConfiguration.shared.lastSentTimestampCache.refresh(threadID) return DatabaseComponent.get(context).mmsSmsDatabase().getConversation(threadID, reverse) } } \ No newline at end of file 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 4e8a079024..64017e2ad9 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 @@ -22,7 +22,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.os.bundleOf -import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.view.marginBottom import dagger.hilt.android.AndroidEntryPoint @@ -39,6 +38,7 @@ import org.session.libsession.utilities.modifyLayoutParams import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 +import org.thoughtcrime.securesms.database.LastSentTimestampCache import org.thoughtcrime.securesms.database.LokiAPIDatabase import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.MmsDatabase @@ -73,6 +73,7 @@ class VisibleMessageView : LinearLayout { @Inject lateinit var mmsSmsDb: MmsSmsDatabase @Inject lateinit var smsDb: SmsDatabase @Inject lateinit var mmsDb: MmsDatabase + @Inject lateinit var lastSentTimestampCache: LastSentTimestampCache private val binding by lazy { ViewVisibleMessageBinding.bind(this) } private val swipeToReplyIcon = ContextCompat.getDrawable(context, R.drawable.ic_baseline_reply_24)!!.mutate() @@ -303,10 +304,6 @@ class VisibleMessageView : LinearLayout { // --- If we got here then we know the message is outgoing --- - val thisUsersSessionId = TextSecurePreferences.getLocalNumber(context) - val lastSentMessageId = mmsSmsDb.getLastSentMessageFromSender(message.threadId, thisUsersSessionId) - val isLastSentMessage = lastSentMessageId == message.id - // ----- Case ii.) Message is outgoing but NOT scheduled to disappear ----- if (!scheduledToDisappear) { // If this isn't a disappearing message then we never show the timer @@ -319,9 +316,11 @@ class VisibleMessageView : LinearLayout { } else { // ..but if the message HAS been successfully sent or read then only display the delivery status // text and image if this is the last sent message. - binding.messageStatusTextView.isVisible = isLastSentMessage - binding.messageStatusImageView.isVisible = isLastSentMessage - if (isLastSentMessage) { binding.messageStatusImageView.bringToFront() } + val lastSentTimestamp = lastSentTimestampCache.getTimestamp(message.threadId) + val isLastSent = lastSentTimestamp == message.timestamp + binding.messageStatusTextView.isVisible = isLastSent + binding.messageStatusImageView.isVisible = isLastSent + if (isLastSent) { binding.messageStatusImageView.bringToFront() } } } else // ----- Case iii.) Message is outgoing AND scheduled to disappear ----- diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyPairUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyPairUtilities.kt index 652732f081..f4887e1adb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyPairUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyPairUtilities.kt @@ -1,10 +1,9 @@ package org.thoughtcrime.securesms.crypto import android.content.Context -import com.goterl.lazysodium.LazySodiumAndroid -import com.goterl.lazysodium.SodiumAndroid import com.goterl.lazysodium.utils.Key import com.goterl.lazysodium.utils.KeyPair +import org.session.libsession.messaging.utilities.SodiumUtilities.sodium import org.session.libsignal.crypto.ecc.DjbECPrivateKey import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.crypto.ecc.ECKeyPair @@ -13,8 +12,6 @@ import org.session.libsignal.utilities.Hex object KeyPairUtilities { - private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } - fun generate(): KeyPairGenerationResult { val seed = sodium.randomBytesBuf(16) try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LastSentTimestampCache.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LastSentTimestampCache.kt new file mode 100644 index 0000000000..46ada7aa9a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LastSentTimestampCache.kt @@ -0,0 +1,38 @@ +package org.thoughtcrime.securesms.database + +import org.session.libsession.messaging.LastSentTimestampCache +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LastSentTimestampCache @Inject constructor( + val mmsSmsDatabase: MmsSmsDatabase +): LastSentTimestampCache { + + private val map = mutableMapOf() + + @Synchronized + override fun getTimestamp(threadId: Long): Long? = map[threadId] + + @Synchronized + override fun submitTimestamp(threadId: Long, timestamp: Long) { + if (map[threadId]?.let { timestamp <= it } == true) return + + map[threadId] = timestamp + } + + @Synchronized + override fun delete(threadId: Long, timestamps: List) { + if (map[threadId]?.let { it !in timestamps } == true) return + map.remove(threadId) + refresh(threadId) + } + + @Synchronized + override fun refresh(threadId: Long) { + if (map[threadId]?.let { it > 0 } == true) return + val lastOutgoingTimestamp = mmsSmsDatabase.getLastOutgoingTimestamp(threadId) + if (lastOutgoingTimestamp <= 0) return + map[threadId] = lastOutgoingTimestamp + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 1e1cc50896..e6bc04e364 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -9,7 +9,11 @@ public interface MmsSmsColumns { public static final String THREAD_ID = "thread_id"; public static final String READ = "read"; public static final String BODY = "body"; + + // This is the address of the message recipient, which may be a single user, a group, or a community! + // It is NOT the address of the sender of any given message! public static final String ADDRESS = "address"; + public static final String ADDRESS_DEVICE_ID = "address_device_id"; public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count"; public static final String READ_RECEIPT_COUNT = "read_receipt_count"; 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 da4f39f0c1..f25689a380 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -163,6 +163,53 @@ public class MmsSmsDatabase extends Database { return null; } + public @Nullable MessageRecord getSentMessageFor(long timestamp, String serializedAuthor) { + // Early exit if the author is not us + boolean isOwnNumber = Util.isOwnNumber(context, serializedAuthor); + if (!isOwnNumber) { + Log.i(TAG, "Asked to find sent messages but provided author is not us - returning null."); + return null; + } + + try (Cursor cursor = queryTables(PROJECTION, MmsSmsColumns.NORMALIZED_DATE_SENT + " = " + timestamp, null, null)) { + MmsSmsDatabase.Reader reader = readerFor(cursor); + + MessageRecord messageRecord; + while ((messageRecord = reader.getNext()) != null) { + if (messageRecord.isOutgoing()) + { + return messageRecord; + } + } + } + Log.i(TAG, "Could not find any message sent from us at provided timestamp - returning null."); + return null; + } + + public MessageRecord getLastSentMessageRecordFromSender(long threadId, String serializedAuthor) { + // Early exit if the author is not us + boolean isOwnNumber = Util.isOwnNumber(context, serializedAuthor); + if (!isOwnNumber) { + Log.i(TAG, "Asked to find last sent message but provided author is not us - returning null."); + return null; + } + + String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; + String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; + + // Try everything with resources so that they auto-close on end of scope + try (Cursor cursor = queryTables(PROJECTION, selection, order, null)) { + try (MmsSmsDatabase.Reader reader = readerFor(cursor)) { + MessageRecord messageRecord; + while ((messageRecord = reader.getNext()) != null) { + if (messageRecord.isOutgoing()) { return messageRecord; } + } + } + } + Log.i(TAG, "Could not find last sent message from us in given thread - returning null."); + return null; + } + public @Nullable MessageRecord getMessageFor(long timestamp, Address author) { return getMessageFor(timestamp, author.serialize()); } @@ -295,15 +342,7 @@ public class MmsSmsDatabase extends Database { return identifiedMessages; } - public long getLastSentMessageFromSender(long threadId, String serializedAuthor) { - - // Early exit - boolean isOwnNumber = Util.isOwnNumber(context, serializedAuthor); - if (!isOwnNumber) { - Log.i(TAG, "Asked to find last sent message but sender isn't us - returning null."); - return -1; - } - + public long getLastOutgoingTimestamp(long threadId) { String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; @@ -311,8 +350,13 @@ public class MmsSmsDatabase extends Database { try (Cursor cursor = queryTables(PROJECTION, selection, order, null)) { try (MmsSmsDatabase.Reader reader = readerFor(cursor)) { MessageRecord messageRecord; + long attempts = 0; + long maxAttempts = 20; while ((messageRecord = reader.getNext()) != null) { - if (messageRecord.isOutgoing()) { return messageRecord.id; } + // Note: We rely on the message order to get us the most recent outgoing message - so we + // take the first outgoing message we find as the last outgoing message. + if (messageRecord.isOutgoing()) return messageRecord.getTimestamp(); + if (attempts++ > maxAttempts) break; } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt index 7fb9c12ab4..5f218a7a9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt @@ -17,6 +17,7 @@ import org.session.libsession.messaging.jobs.MessageReceiveParameters import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.messaging.utilities.SodiumUtilities +import org.session.libsession.messaging.utilities.SodiumUtilities.sodium import org.session.libsession.utilities.bencode.Bencode import org.session.libsession.utilities.bencode.BencodeList import org.session.libsession.utilities.bencode.BencodeString @@ -28,7 +29,6 @@ import javax.inject.Inject private const val TAG = "PushHandler" class PushReceiver @Inject constructor(@ApplicationContext val context: Context) { - private val sodium = LazySodiumAndroid(SodiumAndroid()) private val json = Json { ignoreUnknownKeys = true } fun onPush(dataMap: Map?) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt index 4bef45ff97..bf16333b15 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt @@ -18,6 +18,8 @@ import org.session.libsession.messaging.sending_receiving.notifications.Subscrip import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse import org.session.libsession.messaging.sending_receiving.notifications.UnsubscriptionRequest +import org.session.libsession.messaging.utilities.SodiumUtilities +import org.session.libsession.messaging.utilities.SodiumUtilities.sodium import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.Version @@ -34,8 +36,6 @@ private const val maxRetryCount = 4 @Singleton class PushRegistryV2 @Inject constructor(private val pushReceiver: PushReceiver) { - private val sodium = LazySodiumAndroid(SodiumAndroid()) - fun register( device: Device, token: String, diff --git a/libsession/src/main/java/org/session/libsession/messaging/LastSentTimestampCache.kt b/libsession/src/main/java/org/session/libsession/messaging/LastSentTimestampCache.kt new file mode 100644 index 0000000000..a41ba60c80 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/LastSentTimestampCache.kt @@ -0,0 +1,9 @@ +package org.session.libsession.messaging + +interface LastSentTimestampCache { + fun getTimestamp(threadId: Long): Long? + fun submitTimestamp(threadId: Long, timestamp: Long) + fun delete(threadId: Long, timestamps: List) + fun delete(threadId: Long, timestamp: Long) = delete(threadId, listOf(timestamp)) + fun refresh(threadId: Long) +} diff --git a/libsession/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt b/libsession/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt index 3d48325bf7..e4f15b2114 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt @@ -13,7 +13,8 @@ class MessagingModuleConfiguration( val device: Device, val messageDataProvider: MessageDataProvider, val getUserED25519KeyPair: () -> KeyPair?, - val configFactory: ConfigFactoryProtocol + val configFactory: ConfigFactoryProtocol, + val lastSentTimestampCache: LastSentTimestampCache ) { companion object { diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt index 1f23a1cc87..a5203827ea 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt @@ -21,6 +21,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller.Companion.maxInactivityPeriod import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SodiumUtilities +import org.session.libsession.messaging.utilities.SodiumUtilities.sodium import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.OnionResponse import org.session.libsession.snode.SnodeAPI @@ -48,7 +49,6 @@ object OpenGroupApi { val defaultRooms = MutableSharedFlow>(replay = 1) private val hasPerformedInitialPoll = mutableMapOf() private var hasUpdatedLastOpenDate = false - private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } private val timeSinceLastOpen by lazy { val context = MessagingModuleConfiguration.shared.context val lastOpenDate = TextSecurePreferences.getLastOpenTimeDate(context) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageDecrypter.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageDecrypter.kt index 53bf12f26e..8043da4b74 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageDecrypter.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageDecrypter.kt @@ -8,6 +8,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.sending_receiving.MessageReceiver.Error import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SodiumUtilities +import org.session.libsession.messaging.utilities.SodiumUtilities.sodium import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix @@ -17,8 +18,6 @@ import org.session.libsignal.utilities.removingIdPrefixIfNeeded object MessageDecrypter { - private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } - /** * Decrypts `ciphertext` using the Session protocol and `x25519KeyPair`. * diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageEncrypter.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageEncrypter.kt index 24a620f8d4..361feff9e8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageEncrypter.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageEncrypter.kt @@ -7,6 +7,7 @@ import com.goterl.lazysodium.interfaces.Sign import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.sending_receiving.MessageSender.Error import org.session.libsession.messaging.utilities.SodiumUtilities +import org.session.libsession.messaging.utilities.SodiumUtilities.sodium import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log @@ -14,8 +15,6 @@ import org.session.libsignal.utilities.removingIdPrefixIfNeeded object MessageEncrypter { - private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } - /** * Encrypts `plaintext` using the Session protocol for `hexEncodedX25519PublicKey`. * diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index b0459de1d6..0968db27e2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -73,6 +73,7 @@ object MessageSender { // Convenience fun send(message: Message, destination: Destination, isSyncMessage: Boolean): Promise { + if (message is VisibleMessage) MessagingModuleConfiguration.shared.lastSentTimestampCache.submitTimestamp(message.threadID!!, message.sentTimestamp!!) return if (destination is Destination.LegacyOpenGroup || destination is Destination.OpenGroup || destination is Destination.OpenGroupInbox) { sendToOpenGroupDestination(destination, message) } else { @@ -372,6 +373,7 @@ object MessageSender { // Result Handling private fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) { + if (message is VisibleMessage) MessagingModuleConfiguration.shared.lastSentTimestampCache.submitTimestamp(message.threadID!!, openGroupSentTimestamp) val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! val timestamp = message.sentTimestamp!! diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 6be9c5b058..e65472c1fe 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -290,6 +290,7 @@ fun MessageReceiver.handleVisibleMessage( ): Long? { val storage = MessagingModuleConfiguration.shared.storage val context = MessagingModuleConfiguration.shared.context + message.takeIf { it.isSenderSelf }?.sentTimestamp?.let { MessagingModuleConfiguration.shared.lastSentTimestampCache.submitTimestamp(threadId, it) } val userPublicKey = storage.getUserPublicKey() val messageSender: String? = message.sender @@ -410,12 +411,7 @@ fun MessageReceiver.handleVisibleMessage( message.hasMention = listOf(userPublicKey, userBlindedKey) .filterNotNull() .any { key -> - return@any ( - messageText != null && - messageText.contains("@$key") - ) || ( - (quoteModel?.author?.serialize() ?: "") == key - ) + messageText?.contains("@$key") == true || key == (quoteModel?.author?.serialize() ?: "") } // Persist the message diff --git a/libsession/src/main/java/org/session/libsession/messaging/utilities/SodiumUtilities.kt b/libsession/src/main/java/org/session/libsession/messaging/utilities/SodiumUtilities.kt index 079caee235..38e6080950 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/SodiumUtilities.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/SodiumUtilities.kt @@ -14,7 +14,7 @@ import org.whispersystems.curve25519.Curve25519 import kotlin.experimental.xor object SodiumUtilities { - private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } + val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } private val curve by lazy { Curve25519.getInstance(Curve25519.BEST) } private const val SCALAR_LENGTH: Int = 32 // crypto_core_ed25519_scalarbytes diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 4064aba62d..0f996bacac 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -3,8 +3,6 @@ package org.session.libsession.snode import android.os.Build -import com.goterl.lazysodium.LazySodiumAndroid -import com.goterl.lazysodium.SodiumAndroid import com.goterl.lazysodium.exceptions.SodiumException import com.goterl.lazysodium.interfaces.GenericHash import com.goterl.lazysodium.interfaces.PwHash @@ -19,6 +17,7 @@ import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.task import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.utilities.MessageWrapper +import org.session.libsession.messaging.utilities.SodiumUtilities.sodium import org.session.libsignal.crypto.getRandomElement import org.session.libsignal.database.LokiAPIDatabaseProtocol import org.session.libsignal.protos.SignalServiceProtos @@ -41,7 +40,6 @@ import kotlin.collections.set import kotlin.properties.Delegates.observable object SnodeAPI { - private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } internal val database: LokiAPIDatabaseProtocol get() = SnodeModule.shared.storage private val broadcaster: Broadcaster diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/ThreadUtils.kt b/libsignal/src/main/java/org/session/libsignal/utilities/ThreadUtils.kt index ac81564bd1..e920d85b47 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/ThreadUtils.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/ThreadUtils.kt @@ -1,23 +1,60 @@ package org.session.libsignal.utilities import android.os.Process -import java.util.concurrent.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.SynchronousQueue +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.TimeUnit object ThreadUtils { + const val TAG = "ThreadUtils" + const val PRIORITY_IMPORTANT_BACKGROUND_THREAD = Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE - val executorPool: ExecutorService = Executors.newCachedThreadPool() + // Paraphrased from: https://www.baeldung.com/kotlin/create-thread-pool + // "A cached thread pool such as one created via: + // `val executorPool: ExecutorService = Executors.newCachedThreadPool()` + // will utilize resources according to the requirements of submitted tasks. It will try to reuse + // existing threads for submitted tasks but will create as many threads as it needs if new tasks + // keep pouring in (with a memory usage of at least 1MB per created thread). These threads will + // live for up to 60 seconds of idle time before terminating by default. As such, it presents a + // very sharp tool that doesn't include any backpressure mechanism - and a sudden peak in load + // can bring the system down with an OutOfMemory error. We can achieve a similar effect but with + // better control by creating a ThreadPoolExecutor manually." + + private val corePoolSize = Runtime.getRuntime().availableProcessors() // Default thread pool size is our CPU core count + private val maxPoolSize = corePoolSize * 4 // Allow a maximum pool size of up to 4 threads per core + private val keepAliveTimeSecs = 100L // How long to keep idle threads in the pool before they are terminated + private val workQueue = SynchronousQueue() + val executorPool: ExecutorService = ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTimeSecs, TimeUnit.SECONDS, workQueue) + + // Note: To see how many threads are running in our app at any given time we can use: + // val threadCount = getAllStackTraces().size @JvmStatic fun queue(target: Runnable) { - executorPool.execute(target) + executorPool.execute { + try { + target.run() + } catch (e: Exception) { + Log.e(TAG, e) + } + } } fun queue(target: () -> Unit) { - executorPool.execute(target) + executorPool.execute { + try { + target() + } catch (e: Exception) { + Log.e(TAG, e) + } + } } + // Thread executor used by the audio recorder only @JvmStatic fun newDynamicSingleThreadedExecutor(): ExecutorService { val executor = ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, LinkedBlockingQueue())