From a2a112e8b1cfd7c5caa2a7fb95fc7819a4887650 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 17 May 2024 12:54:57 +0930 Subject: [PATCH 01/13] Prevent reloading sodium (#1485) --- .../org/thoughtcrime/securesms/crypto/KeyPairUtilities.kt | 5 +---- .../org/thoughtcrime/securesms/notifications/PushReceiver.kt | 2 +- .../thoughtcrime/securesms/notifications/PushRegistryV2.kt | 4 ++-- .../session/libsession/messaging/open_groups/OpenGroupApi.kt | 2 +- .../messaging/sending_receiving/MessageDecrypter.kt | 3 +-- .../messaging/sending_receiving/MessageEncrypter.kt | 3 +-- .../libsession/messaging/utilities/SodiumUtilities.kt | 2 +- .../src/main/java/org/session/libsession/snode/SnodeAPI.kt | 4 +--- 8 files changed, 9 insertions(+), 16 deletions(-) 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/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/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/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 From 8c31c83fc59155dd6e53ae25cd9f9474514da1a1 Mon Sep 17 00:00:00 2001 From: alansley Date: Wed, 15 May 2024 13:09:18 +1000 Subject: [PATCH 02/13] Fixes #1483 --- .../conversation/v2/ConversationActivityV2.kt | 23 +++++++++++++++---- .../securesms/database/MmsSmsDatabase.java | 7 +++++- 2 files changed, 24 insertions(+), 6 deletions(-) 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 84f43e014b..71ada31293 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 @@ -298,6 +298,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private val reverseMessageList = false private val adapter by lazy { + + // To prevent repeated attachment download jobs being spawned we'll keep a set of what + // attachmentId / mmsId pairs we've already attempted to download and only spawn the job + // if we haven't already done so. Without this then when the retry limit for a failed job + // hits another job is immediately spawned (endlessly). + var alreadyAttemptedAttachmentDownloadPairs = mutableSetOf>() + val cursor = mmsSmsDb.getConversation(viewModel.threadId, reverseMessageList) val adapter = ConversationAdapter( this, @@ -325,9 +332,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } }, onAttachmentNeedsDownload = { attachmentId, mmsId -> - // Start download (on IO thread) - lifecycleScope.launch(Dispatchers.IO) { - JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) + // Keep track of this specific attachment so we don't download it again + val pair = Pair(attachmentId, mmsId) + if (!alreadyAttemptedAttachmentDownloadPairs.contains(pair)) { + alreadyAttemptedAttachmentDownloadPairs.add(pair) + + // Start download (on IO thread) + lifecycleScope.launch(Dispatchers.IO) { + JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) + } } }, glide = glide, @@ -335,8 +348,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 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..e6685f3ec0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -305,7 +305,12 @@ public class MmsSmsDatabase extends Database { } String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; - String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; + + // As the MmsSmsDatabase.ADDRESS column never contains the sender address we have to get creative to filter down all the + // messages that have been sent without interrogating each MessageRecord returned by the cursor. One way to do this is + // via the fact that the `ADDRESS_DEVICE_ID` is always null for incoming messages, but always has a value (such as 1) for + // outgoing messages - so we'll filter our query for only records with non-null ADDRESS_DEVICE_IDs in the current thread. + String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.ADDRESS_DEVICE_ID + " IS NOT NULL"; // Try everything with resources so that they auto-close on end of scope try (Cursor cursor = queryTables(PROJECTION, selection, order, null)) { From b300b9a743cc9d0c57e0c7681714d795dc5aaa79 Mon Sep 17 00:00:00 2001 From: alansley Date: Wed, 15 May 2024 17:26:48 +1000 Subject: [PATCH 03/13] Addressed PR feedback --- .../conversation/v2/ConversationActivityV2.kt | 17 ++++++------- .../conversation/v2/ConversationAdapter.kt | 6 ++--- .../v2/messages/VisibleMessageView.kt | 6 ++--- .../securesms/database/MmsSmsColumns.java | 4 ++++ .../securesms/database/MmsSmsDatabase.java | 24 ++++++------------- 5 files changed, 22 insertions(+), 35 deletions(-) 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 71ada31293..06681f75cf 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 @@ -299,11 +299,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private val adapter by lazy { - // To prevent repeated attachment download jobs being spawned we'll keep a set of what - // attachmentId / mmsId pairs we've already attempted to download and only spawn the job - // if we haven't already done so. Without this then when the retry limit for a failed job - // hits another job is immediately spawned (endlessly). - var alreadyAttemptedAttachmentDownloadPairs = mutableSetOf>() + // To prevent repeated attachment download jobs being spawned we'll keep track of the + // attachment Ids we've attempted to download, and only spawn job if we haven't already + // tried. Without this then when the retry limit for a failed job hits another job is + // immediately spawned (endlessly). + val alreadyAttemptedAttachmentDownloads = mutableSetOf() val cursor = mmsSmsDb.getConversation(viewModel.threadId, reverseMessageList) val adapter = ConversationAdapter( @@ -332,12 +332,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } }, onAttachmentNeedsDownload = { attachmentId, mmsId -> - // Keep track of this specific attachment so we don't download it again - val pair = Pair(attachmentId, mmsId) - if (!alreadyAttemptedAttachmentDownloadPairs.contains(pair)) { - alreadyAttemptedAttachmentDownloadPairs.add(pair) - // Start download (on IO thread) + alreadyAttemptedAttachmentDownloads.takeUnless { attachmentId in alreadyAttemptedAttachmentDownloads }.let { + alreadyAttemptedAttachmentDownloads += attachmentId lifecycleScope.launch(Dispatchers.IO) { JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 371df34565..267a71c0b3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -139,8 +139,7 @@ class ConversationAdapter( senderId, lastSeen.get(), visibleMessageViewDelegate, - onAttachmentNeedsDownload, - lastSentMessageId + onAttachmentNeedsDownload ) if (!message.isDeleted) { @@ -216,8 +215,7 @@ class ConversationAdapter( if (cursorHasContent) { val thisThreadId = cursor.getLong(4) // Column index 4 is "thread_id" if (thisThreadId != -1L) { - val thisUsersSessionId = TextSecurePreferences.getLocalNumber(context) - return messageDB.getLastSentMessageFromSender(thisThreadId, thisUsersSessionId) + return messageDB.getLastOutgoingMessage(thisThreadId) } } return -1L 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..5a0da5265c 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 @@ -134,8 +134,7 @@ class VisibleMessageView : LinearLayout { senderSessionID: String, lastSeen: Long, delegate: VisibleMessageViewDelegate? = null, - onAttachmentNeedsDownload: (Long, Long) -> Unit, - lastSentMessageId: Long + onAttachmentNeedsDownload: (Long, Long) -> Unit ) { replyDisabled = message.isOpenGroupInvitation val threadID = message.threadId @@ -303,8 +302,7 @@ 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 lastSentMessageId = mmsSmsDb.getLastOutgoingMessage(message.threadId) val isLastSentMessage = lastSentMessageId == message.id // ----- Case ii.) Message is outgoing but NOT scheduled to disappear ----- 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 e6685f3ec0..54141311d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -295,29 +295,19 @@ 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 getLastOutgoingMessage(long threadId) { String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; + String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; - // As the MmsSmsDatabase.ADDRESS column never contains the sender address we have to get creative to filter down all the - // messages that have been sent without interrogating each MessageRecord returned by the cursor. One way to do this is - // via the fact that the `ADDRESS_DEVICE_ID` is always null for incoming messages, but always has a value (such as 1) for - // outgoing messages - so we'll filter our query for only records with non-null ADDRESS_DEVICE_IDs in the current thread. - String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.ADDRESS_DEVICE_ID + " IS NOT NULL"; - - // Try everything with resources so that they auto-close on end of scope + // Try everything with resources so that they auto-close on end of scope. + // Note: Do NOT call cursor.moveToFirst() once we have it, for reasons unknown to me it breaks the functionality. -AL 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.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. + if (messageRecord.isOutgoing()) return messageRecord.id; } } } From 9cf30dd67e5e98916f2a22db835d61967e7f078c Mon Sep 17 00:00:00 2001 From: alansley Date: Thu, 16 May 2024 09:42:02 +1000 Subject: [PATCH 04/13] Minor phrasing & indentation adjustments --- .../conversation/v2/ConversationActivityV2.kt | 12 +++++++----- .../securesms/database/MmsSmsDatabase.java | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) 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 06681f75cf..508aebdef2 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 @@ -299,10 +299,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private val adapter by lazy { - // To prevent repeated attachment download jobs being spawned we'll keep track of the - // attachment Ids we've attempted to download, and only spawn job if we haven't already - // tried. Without this then when the retry limit for a failed job hits another job is - // immediately spawned (endlessly). + // To prevent repeated attachment download jobs being spawned for any that fail we'll keep + // track of the attachment Ids we've attempted to download. Without this guard mechanism + // then when the retry limit for a failed job is reached another job is immediately spawned + // to download the same attachment (endlessly). val alreadyAttemptedAttachmentDownloads = mutableSetOf() val cursor = mmsSmsDb.getConversation(viewModel.threadId, reverseMessageList) @@ -333,7 +333,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe }, onAttachmentNeedsDownload = { attachmentId, mmsId -> - alreadyAttemptedAttachmentDownloads.takeUnless { attachmentId in alreadyAttemptedAttachmentDownloads }.let { + alreadyAttemptedAttachmentDownloads.takeUnless { + attachmentId in alreadyAttemptedAttachmentDownloads + }.let { alreadyAttemptedAttachmentDownloads += attachmentId lifecycleScope.launch(Dispatchers.IO) { JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) 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 54141311d4..7dfbf3dcde 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -306,7 +306,7 @@ public class MmsSmsDatabase extends Database { MessageRecord messageRecord; while ((messageRecord = reader.getNext()) != null) { // Note: We rely on the message order to get us the most recent outgoing message - so we - // take the first outgoing message we find. + // take the first outgoing message we find as the last outgoing message. if (messageRecord.isOutgoing()) return messageRecord.id; } } From 4bef09a3c1c1cad6eee036465b44c2b0f4de9880 Mon Sep 17 00:00:00 2001 From: alansley Date: Thu, 16 May 2024 15:02:16 +1000 Subject: [PATCH 05/13] Removed comment following PR feedback --- .../org/thoughtcrime/securesms/database/MmsSmsDatabase.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 7dfbf3dcde..07900e4a9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -299,8 +299,7 @@ public class MmsSmsDatabase extends Database { 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. - // Note: Do NOT call cursor.moveToFirst() once we have it, for reasons unknown to me it breaks the functionality. -AL + // 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; From c312c27dd35b4cb6c381e1f1f65d290a6a0fab87 Mon Sep 17 00:00:00 2001 From: Al Lansley Date: Fri, 17 May 2024 10:08:47 +1000 Subject: [PATCH 06/13] Reduce frequency of calls to find last sent message --- .../conversation/v2/ConversationActivityV2.kt | 10 ++++++++++ .../securesms/conversation/v2/ConversationAdapter.kt | 8 ++------ .../conversation/v2/messages/VisibleMessageView.kt | 3 ++- 3 files changed, 14 insertions(+), 7 deletions(-) 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 508aebdef2..c4e1b59d01 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 @@ -372,6 +372,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private var currentLastVisibleRecyclerViewIndex: Int = RecyclerView.NO_POSITION private var recyclerScrollState: Int = RecyclerView.SCROLL_STATE_IDLE + // region Settings companion object { // Extras @@ -387,6 +388,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe const val PICK_FROM_LIBRARY = 12 const val INVITE_CONTACTS = 124 + var lastSentMessageId = -1L; } // endregion @@ -513,6 +515,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe viewModel.run { binding?.toolbarContent?.update(recipient ?: return, openGroup, expirationConfiguration) } + + // Update our last sent message Id on startup / resume (resume is called after onCreate) + lastSentMessageId = mmsSmsDb.getLastOutgoingMessage(viewModel.threadId) } override fun onPause() { @@ -2221,6 +2226,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // to the bottom of long messages as required by Jira SES-789 / GitHub 1364). recyclerView.scrollToPosition(adapter.itemCount) } + + // Update our cached last sent message to ensure we have accurate details. + // Note: This `onChanged` method is not triggered when scrolling so should minimally + // affect performance. + lastSentMessageId = mmsSmsDb.getLastOutgoingMessage(viewModel.threadId) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 267a71c0b3..e4c5848229 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -59,7 +59,8 @@ class ConversationAdapter( private val contactCache = SparseArray(100) private val contactLoadedCache = SparseBooleanArray(100) private val lastSeen = AtomicLong(originalLastSeen) - private var lastSentMessageId: Long = -1L + + //private var lastSentMessageId: Long = -1L init { lifecycleCoroutineScope.launch(IO) { @@ -241,11 +242,6 @@ class ConversationAdapter( toDeselect.iterator().forEach { (pos, record) -> onDeselect(record, pos) } - - // This value gets updated here ONLY when the cursor changes, and the value is then passed - // through to `VisibleMessageView.bind` each time we bind via `onBindItemViewHolder`, above. - // If there are no messages then lastSentMessageId is assigned the value -1L. - if (cursor != null) { lastSentMessageId = getLastSentMessageId(cursor) } } fun findLastSeenItemPosition(lastSeenTimestamp: Long): Int? { 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 5a0da5265c..fd6e9200b0 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 @@ -302,7 +302,8 @@ class VisibleMessageView : LinearLayout { // --- If we got here then we know the message is outgoing --- - val lastSentMessageId = mmsSmsDb.getLastOutgoingMessage(message.threadId) + //val lastSentMessageId = mmsSmsDb.getLastOutgoingMessage(message.threadId) + val lastSentMessageId = ConversationActivityV2.lastSentMessageId; val isLastSentMessage = lastSentMessageId == message.id // ----- Case ii.) Message is outgoing but NOT scheduled to disappear ----- From f6275362eaf570fba2e424460d0661883d452a8c Mon Sep 17 00:00:00 2001 From: Al Lansley Date: Fri, 17 May 2024 10:17:55 +1000 Subject: [PATCH 07/13] Removed 2 (two) accidentally left in commented lines --- .../securesms/conversation/v2/ConversationAdapter.kt | 2 -- .../securesms/conversation/v2/messages/VisibleMessageView.kt | 1 - 2 files changed, 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index e4c5848229..8f12aab7aa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -60,8 +60,6 @@ class ConversationAdapter( private val contactLoadedCache = SparseBooleanArray(100) private val lastSeen = AtomicLong(originalLastSeen) - //private var lastSentMessageId: Long = -1L - init { lifecycleCoroutineScope.launch(IO) { while (isActive) { 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 fd6e9200b0..65e168e8f3 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 @@ -302,7 +302,6 @@ class VisibleMessageView : LinearLayout { // --- If we got here then we know the message is outgoing --- - //val lastSentMessageId = mmsSmsDb.getLastOutgoingMessage(message.threadId) val lastSentMessageId = ConversationActivityV2.lastSentMessageId; val isLastSentMessage = lastSentMessageId == message.id From 44f47662a76d78ee2e2c00ddb031890cd73cfc7d Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 17 May 2024 14:07:33 +0930 Subject: [PATCH 08/13] Update version 1.18.3 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9e231c1bec..5b52584b28 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,8 +31,8 @@ configurations.all { exclude module: "commons-logging" } -def canonicalVersionCode = 370 -def canonicalVersionName = "1.18.2" +def canonicalVersionCode = 371 +def canonicalVersionName = "1.18.3" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From cf13caaee49543fc089f95f2f6610af3ec1a937b Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 17 May 2024 16:25:19 +0930 Subject: [PATCH 09/13] Bump canonicalVersionCode --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5b52584b28..a4c0e8f8e2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,7 +31,7 @@ configurations.all { exclude module: "commons-logging" } -def canonicalVersionCode = 371 +def canonicalVersionCode = 372 def canonicalVersionName = "1.18.3" def postFixSize = 10 From 4904524af5cb53038afdf14044f686f8120ab5c7 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 20 May 2024 13:58:13 +0930 Subject: [PATCH 10/13] Fix excessive last sent calls --- .../securesms/ApplicationContext.java | 5 ++- .../attachments/DatabaseAttachmentProvider.kt | 13 +++++++ .../conversation/v2/ConversationActivityV2.kt | 17 ++------- .../conversation/v2/ConversationAdapter.kt | 15 -------- .../conversation/v2/ConversationLoader.kt | 2 + .../v2/messages/VisibleMessageView.kt | 16 ++++---- .../database/LastSentTimestampCache.kt | 38 +++++++++++++++++++ .../securesms/database/MmsSmsDatabase.java | 7 +++- .../messaging/LastSentTimestampCache.kt | 9 +++++ .../messaging/MessagingModuleConfiguration.kt | 3 +- .../sending_receiving/MessageSender.kt | 1 + .../ReceivedMessageHandler.kt | 8 +--- 12 files changed, 86 insertions(+), 48 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/LastSentTimestampCache.kt create mode 100644 libsession/src/main/java/org/session/libsession/messaging/LastSentTimestampCache.kt 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..5c4acb2a11 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 @@ -185,9 +187,15 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) 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 +203,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 c4e1b59d01..2e421c9a10 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 @@ -332,11 +332,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } }, onAttachmentNeedsDownload = { attachmentId, mmsId -> - alreadyAttemptedAttachmentDownloads.takeUnless { - attachmentId in alreadyAttemptedAttachmentDownloads - }.let { - alreadyAttemptedAttachmentDownloads += attachmentId + attachmentId in it + }?.let { + it += attachmentId lifecycleScope.launch(Dispatchers.IO) { JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) } @@ -387,8 +386,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe const val PICK_GIF = 10 const val PICK_FROM_LIBRARY = 12 const val INVITE_CONTACTS = 124 - - var lastSentMessageId = -1L; } // endregion @@ -515,9 +512,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe viewModel.run { binding?.toolbarContent?.update(recipient ?: return, openGroup, expirationConfiguration) } - - // Update our last sent message Id on startup / resume (resume is called after onCreate) - lastSentMessageId = mmsSmsDb.getLastOutgoingMessage(viewModel.threadId) } override fun onPause() { @@ -2226,11 +2220,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // to the bottom of long messages as required by Jira SES-789 / GitHub 1364). recyclerView.scrollToPosition(adapter.itemCount) } - - // Update our cached last sent message to ensure we have accurate details. - // Note: This `onChanged` method is not triggered when scrolling so should minimally - // affect performance. - lastSentMessageId = mmsSmsDb.getLastOutgoingMessage(viewModel.threadId) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 8f12aab7aa..36d64212d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -22,12 +22,10 @@ import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageBinding import org.session.libsession.messaging.contacts.Contact -import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter -import org.thoughtcrime.securesms.database.MmsSmsColumns import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.mms.GlideRequests @@ -207,19 +205,6 @@ class ConversationAdapter( return messageDB.readerFor(cursor).current } - private fun getLastSentMessageId(cursor: Cursor): Long { - // If we don't move to first (or at least step backwards) we can step off the end of the - // cursor and any query will return an "Index = -1" error. - val cursorHasContent = cursor.moveToFirst() - if (cursorHasContent) { - val thisThreadId = cursor.getLong(4) // Column index 4 is "thread_id" - if (thisThreadId != -1L) { - return messageDB.getLastOutgoingMessage(thisThreadId) - } - } - return -1L - } - override fun changeCursor(cursor: Cursor?) { super.changeCursor(cursor) 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 65e168e8f3..e0ea2f8746 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 @@ -32,13 +31,12 @@ import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact.ContactContext import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.utilities.Address -import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.getColorFromAttr 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 +71,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() @@ -302,9 +301,6 @@ class VisibleMessageView : LinearLayout { // --- If we got here then we know the message is outgoing --- - val lastSentMessageId = ConversationActivityV2.lastSentMessageId; - 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 @@ -317,9 +313,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/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/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 07900e4a9f..c8f34c91e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -295,7 +295,7 @@ public class MmsSmsDatabase extends Database { return identifiedMessages; } - public long getLastOutgoingMessage(long threadId) { + public long getLastOutgoingTimestamp(long threadId) { String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; @@ -303,10 +303,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) { // 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.id; + if (messageRecord.isOutgoing()) return messageRecord.getTimestamp(); + if (attempts++ > maxAttempts) break; } } } 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/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index b0459de1d6..2f43db8933 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 { 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..fc5ac8e2a2 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.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 From f114ac84bd252cc11b3bb8130ac20a37af1f78f5 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 20 May 2024 14:05:34 +0930 Subject: [PATCH 11/13] Fix last sent updated by non-self --- .../messaging/sending_receiving/ReceivedMessageHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fc5ac8e2a2..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,7 +290,7 @@ fun MessageReceiver.handleVisibleMessage( ): Long? { val storage = MessagingModuleConfiguration.shared.storage val context = MessagingModuleConfiguration.shared.context - message.sentTimestamp?.let { MessagingModuleConfiguration.shared.lastSentTimestampCache.submitTimestamp(threadId, it) } + message.takeIf { it.isSenderSelf }?.sentTimestamp?.let { MessagingModuleConfiguration.shared.lastSentTimestampCache.submitTimestamp(threadId, it) } val userPublicKey = storage.getUserPublicKey() val messageSender: String? = message.sender From a660f45043fe4d36012a70d392b97e46fdc89eb2 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 21 May 2024 11:27:06 +0930 Subject: [PATCH 12/13] Fix timestamp cache for communities --- .../libsession/messaging/sending_receiving/MessageSender.kt | 1 + 1 file changed, 1 insertion(+) 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 2f43db8933..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 @@ -373,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!! From f6fb4ab78ce0550d86d145f7207025c80266d83b Mon Sep 17 00:00:00 2001 From: alansley Date: Wed, 22 May 2024 17:04:44 +1000 Subject: [PATCH 13/13] Attachment download job re-start preventer removed --- .../conversation/v2/ConversationActivityV2.kt | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) 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 2e421c9a10..b884d3e7ca 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 @@ -298,13 +298,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private val reverseMessageList = false private val adapter by lazy { - - // To prevent repeated attachment download jobs being spawned for any that fail we'll keep - // track of the attachment Ids we've attempted to download. Without this guard mechanism - // then when the retry limit for a failed job is reached another job is immediately spawned - // to download the same attachment (endlessly). - val alreadyAttemptedAttachmentDownloads = mutableSetOf() - val cursor = mmsSmsDb.getConversation(viewModel.threadId, reverseMessageList) val adapter = ConversationAdapter( this, @@ -332,13 +325,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } }, onAttachmentNeedsDownload = { attachmentId, mmsId -> - alreadyAttemptedAttachmentDownloads.takeUnless { - attachmentId in it - }?.let { - it += attachmentId - lifecycleScope.launch(Dispatchers.IO) { - JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) - } + lifecycleScope.launch(Dispatchers.IO) { + JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) } }, glide = glide,