Merge remote-tracking branch 'upstream/dev' into ses-1936-oom

This commit is contained in:
fanchao 2024-05-27 13:37:00 +10:00
commit 35335480ca
22 changed files with 192 additions and 54 deletions

View File

@ -31,8 +31,8 @@ configurations.all {
exclude module: "commons-logging" exclude module: "commons-logging"
} }
def canonicalVersionCode = 371 def canonicalVersionCode = 372
def canonicalVersionName = "1.18.2" def canonicalVersionName = "1.18.3"
def postFixSize = 10 def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1, def abiPostFix = ['armeabi-v7a' : 1,

View File

@ -57,6 +57,7 @@ import org.signal.aesgcmprovider.AesGcmProvider;
import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.crypto.KeyPairUtilities; import org.thoughtcrime.securesms.crypto.KeyPairUtilities;
import org.thoughtcrime.securesms.database.EmojiSearchDatabase; import org.thoughtcrime.securesms.database.EmojiSearchDatabase;
import org.thoughtcrime.securesms.database.LastSentTimestampCache;
import org.thoughtcrime.securesms.database.LokiAPIDatabase; import org.thoughtcrime.securesms.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.database.Storage; import org.thoughtcrime.securesms.database.Storage;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
@ -149,6 +150,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
@Inject TextSecurePreferences textSecurePreferences; @Inject TextSecurePreferences textSecurePreferences;
@Inject PushRegistry pushRegistry; @Inject PushRegistry pushRegistry;
@Inject ConfigFactory configFactory; @Inject ConfigFactory configFactory;
@Inject LastSentTimestampCache lastSentTimestampCache;
CallMessageProcessor callMessageProcessor; CallMessageProcessor callMessageProcessor;
MessagingModuleConfiguration messagingModuleConfiguration; MessagingModuleConfiguration messagingModuleConfiguration;
@ -218,7 +220,8 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
device, device,
messageDataProvider, messageDataProvider,
()-> KeyPairUtilities.INSTANCE.getUserED25519KeyPair(this), ()-> KeyPairUtilities.INSTANCE.getUserED25519KeyPair(this),
configFactory configFactory,
lastSentTimestampCache
); );
callMessageProcessor = new CallMessageProcessor(this, textSecurePreferences, ProcessLifecycleOwner.get().getLifecycle(), storage); callMessageProcessor = new CallMessageProcessor(this, textSecurePreferences, ProcessLifecycleOwner.get().getLifecycle(), storage);
Log.i(TAG, "onCreate()"); Log.i(TAG, "onCreate()");

View File

@ -5,6 +5,8 @@ import android.text.TextUtils
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.session.libsession.database.MessageDataProvider 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.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState 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) { override fun deleteMessage(messageID: Long, isSms: Boolean) {
val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase() val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase()
else DatabaseComponent.get(context).mmsDatabase() else DatabaseComponent.get(context).mmsDatabase()
val (threadId, timestamp) = runCatching { messagingDatabase.getMessageRecord(messageID).run { threadId to timestamp } }.getOrNull() ?: (null to null)
messagingDatabase.deleteMessage(messageID) messagingDatabase.deleteMessage(messageID)
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessage(messageID, isSms) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessage(messageID, isSms)
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHash(messageID, mms = !isSms) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHash(messageID, mms = !isSms)
threadId ?: return
timestamp ?: return
MessagingModuleConfiguration.shared.lastSentTimestampCache.delete(threadId, timestamp)
} }
override fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean) { override fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean) {
@ -195,12 +202,17 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase() val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase()
else DatabaseComponent.get(context).mmsDatabase() else DatabaseComponent.get(context).mmsDatabase()
val messages = messageIDs.mapNotNull { runCatching { messagingDatabase.getMessageRecord(it) }.getOrNull() }
// Perform local delete // Perform local delete
messagingDatabase.deleteMessages(messageIDs.toLongArray(), threadId) messagingDatabase.deleteMessages(messageIDs.toLongArray(), threadId)
// Perform online delete // Perform online delete
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessages(messageIDs) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessages(messageIDs)
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHashes(messageIDs, mms = !isSms) 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? { override fun updateMessageAsDeleted(timestamp: Long, author: String): Long? {

View File

@ -327,7 +327,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
}, },
onAttachmentNeedsDownload = { attachmentId, mmsId -> onAttachmentNeedsDownload = { attachmentId, mmsId ->
// Start download (on IO thread)
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId))
} }
@ -337,8 +336,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
) )
adapter.visibleMessageViewDelegate = this adapter.visibleMessageViewDelegate = this
// Register an AdapterDataObserver to scroll us to the bottom of the RecyclerView if we're // Register an AdapterDataObserver to scroll us to the bottom of the RecyclerView for if
// already near the the bottom and the data changes. // we're already near the the bottom and the data changes.
adapter.registerAdapterDataObserver(ConversationAdapterDataObserver(binding?.conversationRecyclerView!!, adapter)) adapter.registerAdapterDataObserver(ConversationAdapterDataObserver(binding?.conversationRecyclerView!!, adapter))
adapter adapter
@ -376,7 +375,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
const val PICK_GIF = 10 const val PICK_GIF = 10
const val PICK_FROM_LIBRARY = 12 const val PICK_FROM_LIBRARY = 12
const val INVITE_CONTACTS = 124 const val INVITE_CONTACTS = 124
} }
// endregion // endregion
@ -574,7 +572,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
binding!!.conversationRecyclerView.layoutManager = layoutManager binding!!.conversationRecyclerView.layoutManager = layoutManager
// Workaround for the fact that CursorRecyclerViewAdapter doesn't auto-update automatically (even though it says it will) // Workaround for the fact that CursorRecyclerViewAdapter doesn't auto-update automatically (even though it says it will)
LoaderManager.getInstance(this).restartLoader(0, null, this) 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) { 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 // 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.") Log.w("ConversationActivityV2", "Asked to delete messages but could not obtain viewModel recipient - aborting.")
return return
} }
val allSentByCurrentUser = messages.all { it.isOutgoing } val allSentByCurrentUser = messages.all { it.isOutgoing }
val allHasHash = messages.all { lokiMessageDb.getMessageServerHash(it.id, it.isMms) != null } val allHasHash = messages.all { lokiMessageDb.getMessageServerHash(it.id, it.isMms) != null }

View File

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.util.AbstractCursorLoader import org.thoughtcrime.securesms.util.AbstractCursorLoader
@ -12,6 +13,7 @@ class ConversationLoader(
) : AbstractCursorLoader(context) { ) : AbstractCursorLoader(context) {
override fun getCursor(): Cursor { override fun getCursor(): Cursor {
MessagingModuleConfiguration.shared.lastSentTimestampCache.refresh(threadID)
return DatabaseComponent.get(context).mmsSmsDatabase().getConversation(threadID, reverse) return DatabaseComponent.get(context).mmsSmsDatabase().getConversation(threadID, reverse)
} }
} }

View File

@ -22,7 +22,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.marginBottom import androidx.core.view.marginBottom
import dagger.hilt.android.AndroidEntryPoint 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.IdPrefix
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.database.LastSentTimestampCache
import org.thoughtcrime.securesms.database.LokiAPIDatabase import org.thoughtcrime.securesms.database.LokiAPIDatabase
import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.LokiThreadDatabase
import org.thoughtcrime.securesms.database.MmsDatabase import org.thoughtcrime.securesms.database.MmsDatabase
@ -73,6 +73,7 @@ class VisibleMessageView : LinearLayout {
@Inject lateinit var mmsSmsDb: MmsSmsDatabase @Inject lateinit var mmsSmsDb: MmsSmsDatabase
@Inject lateinit var smsDb: SmsDatabase @Inject lateinit var smsDb: SmsDatabase
@Inject lateinit var mmsDb: MmsDatabase @Inject lateinit var mmsDb: MmsDatabase
@Inject lateinit var lastSentTimestampCache: LastSentTimestampCache
private val binding by lazy { ViewVisibleMessageBinding.bind(this) } private val binding by lazy { ViewVisibleMessageBinding.bind(this) }
private val swipeToReplyIcon = ContextCompat.getDrawable(context, R.drawable.ic_baseline_reply_24)!!.mutate() 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 --- // --- 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 ----- // ----- Case ii.) Message is outgoing but NOT scheduled to disappear -----
if (!scheduledToDisappear) { if (!scheduledToDisappear) {
// If this isn't a disappearing message then we never show the timer // If this isn't a disappearing message then we never show the timer
@ -319,9 +316,11 @@ class VisibleMessageView : LinearLayout {
} else { } else {
// ..but if the message HAS been successfully sent or read then only display the delivery status // ..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. // text and image if this is the last sent message.
binding.messageStatusTextView.isVisible = isLastSentMessage val lastSentTimestamp = lastSentTimestampCache.getTimestamp(message.threadId)
binding.messageStatusImageView.isVisible = isLastSentMessage val isLastSent = lastSentTimestamp == message.timestamp
if (isLastSentMessage) { binding.messageStatusImageView.bringToFront() } binding.messageStatusTextView.isVisible = isLastSent
binding.messageStatusImageView.isVisible = isLastSent
if (isLastSent) { binding.messageStatusImageView.bringToFront() }
} }
} }
else // ----- Case iii.) Message is outgoing AND scheduled to disappear ----- else // ----- Case iii.) Message is outgoing AND scheduled to disappear -----

View File

@ -1,10 +1,9 @@
package org.thoughtcrime.securesms.crypto package org.thoughtcrime.securesms.crypto
import android.content.Context 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.Key
import com.goterl.lazysodium.utils.KeyPair 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.DjbECPrivateKey
import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.crypto.ecc.DjbECPublicKey
import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.crypto.ecc.ECKeyPair
@ -13,8 +12,6 @@ import org.session.libsignal.utilities.Hex
object KeyPairUtilities { object KeyPairUtilities {
private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) }
fun generate(): KeyPairGenerationResult { fun generate(): KeyPairGenerationResult {
val seed = sodium.randomBytesBuf(16) val seed = sodium.randomBytesBuf(16)
try { try {

View File

@ -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<Long, Long>()
@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<Long>) {
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
}
}

View File

@ -9,7 +9,11 @@ public interface MmsSmsColumns {
public static final String THREAD_ID = "thread_id"; public static final String THREAD_ID = "thread_id";
public static final String READ = "read"; public static final String READ = "read";
public static final String BODY = "body"; 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 = "address";
public static final String ADDRESS_DEVICE_ID = "address_device_id"; public static final String ADDRESS_DEVICE_ID = "address_device_id";
public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count"; public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count";
public static final String READ_RECEIPT_COUNT = "read_receipt_count"; public static final String READ_RECEIPT_COUNT = "read_receipt_count";

View File

@ -163,6 +163,53 @@ public class MmsSmsDatabase extends Database {
return null; 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) { public @Nullable MessageRecord getMessageFor(long timestamp, Address author) {
return getMessageFor(timestamp, author.serialize()); return getMessageFor(timestamp, author.serialize());
} }
@ -295,15 +342,7 @@ public class MmsSmsDatabase extends Database {
return identifiedMessages; return identifiedMessages;
} }
public long getLastSentMessageFromSender(long threadId, String serializedAuthor) { public long getLastOutgoingTimestamp(long threadId) {
// 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;
}
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
@ -311,8 +350,13 @@ public class MmsSmsDatabase extends Database {
try (Cursor cursor = queryTables(PROJECTION, selection, order, null)) { try (Cursor cursor = queryTables(PROJECTION, selection, order, null)) {
try (MmsSmsDatabase.Reader reader = readerFor(cursor)) { try (MmsSmsDatabase.Reader reader = readerFor(cursor)) {
MessageRecord messageRecord; MessageRecord messageRecord;
long attempts = 0;
long maxAttempts = 20;
while ((messageRecord = reader.getNext()) != null) { 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;
} }
} }
} }

View File

@ -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.sending_receiving.notifications.PushNotificationMetadata
import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsession.messaging.utilities.SodiumUtilities 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.Bencode
import org.session.libsession.utilities.bencode.BencodeList import org.session.libsession.utilities.bencode.BencodeList
import org.session.libsession.utilities.bencode.BencodeString import org.session.libsession.utilities.bencode.BencodeString
@ -28,7 +29,6 @@ import javax.inject.Inject
private const val TAG = "PushHandler" private const val TAG = "PushHandler"
class PushReceiver @Inject constructor(@ApplicationContext val context: Context) { class PushReceiver @Inject constructor(@ApplicationContext val context: Context) {
private val sodium = LazySodiumAndroid(SodiumAndroid())
private val json = Json { ignoreUnknownKeys = true } private val json = Json { ignoreUnknownKeys = true }
fun onPush(dataMap: Map<String, String>?) { fun onPush(dataMap: Map<String, String>?) {

View File

@ -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.SubscriptionResponse
import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse
import org.session.libsession.messaging.sending_receiving.notifications.UnsubscriptionRequest 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.OnionRequestAPI
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.Version import org.session.libsession.snode.Version
@ -34,8 +36,6 @@ private const val maxRetryCount = 4
@Singleton @Singleton
class PushRegistryV2 @Inject constructor(private val pushReceiver: PushReceiver) { class PushRegistryV2 @Inject constructor(private val pushReceiver: PushReceiver) {
private val sodium = LazySodiumAndroid(SodiumAndroid())
fun register( fun register(
device: Device, device: Device,
token: String, token: String,

View File

@ -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<Long>)
fun delete(threadId: Long, timestamp: Long) = delete(threadId, listOf(timestamp))
fun refresh(threadId: Long)
}

View File

@ -13,7 +13,8 @@ class MessagingModuleConfiguration(
val device: Device, val device: Device,
val messageDataProvider: MessageDataProvider, val messageDataProvider: MessageDataProvider,
val getUserED25519KeyPair: () -> KeyPair?, val getUserED25519KeyPair: () -> KeyPair?,
val configFactory: ConfigFactoryProtocol val configFactory: ConfigFactoryProtocol,
val lastSentTimestampCache: LastSentTimestampCache
) { ) {
companion object { companion object {

View File

@ -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.sending_receiving.pollers.OpenGroupPoller.Companion.maxInactivityPeriod
import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.messaging.utilities.SodiumUtilities.sodium
import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.snode.OnionResponse import org.session.libsession.snode.OnionResponse
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
@ -48,7 +49,6 @@ object OpenGroupApi {
val defaultRooms = MutableSharedFlow<List<DefaultGroup>>(replay = 1) val defaultRooms = MutableSharedFlow<List<DefaultGroup>>(replay = 1)
private val hasPerformedInitialPoll = mutableMapOf<String, Boolean>() private val hasPerformedInitialPoll = mutableMapOf<String, Boolean>()
private var hasUpdatedLastOpenDate = false private var hasUpdatedLastOpenDate = false
private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) }
private val timeSinceLastOpen by lazy { private val timeSinceLastOpen by lazy {
val context = MessagingModuleConfiguration.shared.context val context = MessagingModuleConfiguration.shared.context
val lastOpenDate = TextSecurePreferences.getLastOpenTimeDate(context) val lastOpenDate = TextSecurePreferences.getLastOpenTimeDate(context)

View File

@ -8,6 +8,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.sending_receiving.MessageReceiver.Error import org.session.libsession.messaging.sending_receiving.MessageReceiver.Error
import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.messaging.utilities.SodiumUtilities.sodium
import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.crypto.ecc.ECKeyPair
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
@ -17,8 +18,6 @@ import org.session.libsignal.utilities.removingIdPrefixIfNeeded
object MessageDecrypter { object MessageDecrypter {
private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) }
/** /**
* Decrypts `ciphertext` using the Session protocol and `x25519KeyPair`. * Decrypts `ciphertext` using the Session protocol and `x25519KeyPair`.
* *

View File

@ -7,6 +7,7 @@ import com.goterl.lazysodium.interfaces.Sign
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.sending_receiving.MessageSender.Error import org.session.libsession.messaging.sending_receiving.MessageSender.Error
import org.session.libsession.messaging.utilities.SodiumUtilities 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.Hex
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
@ -14,8 +15,6 @@ import org.session.libsignal.utilities.removingIdPrefixIfNeeded
object MessageEncrypter { object MessageEncrypter {
private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) }
/** /**
* Encrypts `plaintext` using the Session protocol for `hexEncodedX25519PublicKey`. * Encrypts `plaintext` using the Session protocol for `hexEncodedX25519PublicKey`.
* *

View File

@ -73,6 +73,7 @@ object MessageSender {
// Convenience // Convenience
fun send(message: Message, destination: Destination, isSyncMessage: Boolean): Promise<Unit, Exception> { fun send(message: Message, destination: Destination, isSyncMessage: Boolean): Promise<Unit, Exception> {
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) { return if (destination is Destination.LegacyOpenGroup || destination is Destination.OpenGroup || destination is Destination.OpenGroupInbox) {
sendToOpenGroupDestination(destination, message) sendToOpenGroupDestination(destination, message)
} else { } else {
@ -372,6 +373,7 @@ object MessageSender {
// Result Handling // Result Handling
private fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) { 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 storage = MessagingModuleConfiguration.shared.storage
val userPublicKey = storage.getUserPublicKey()!! val userPublicKey = storage.getUserPublicKey()!!
val timestamp = message.sentTimestamp!! val timestamp = message.sentTimestamp!!

View File

@ -290,6 +290,7 @@ fun MessageReceiver.handleVisibleMessage(
): Long? { ): Long? {
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val context = MessagingModuleConfiguration.shared.context val context = MessagingModuleConfiguration.shared.context
message.takeIf { it.isSenderSelf }?.sentTimestamp?.let { MessagingModuleConfiguration.shared.lastSentTimestampCache.submitTimestamp(threadId, it) }
val userPublicKey = storage.getUserPublicKey() val userPublicKey = storage.getUserPublicKey()
val messageSender: String? = message.sender val messageSender: String? = message.sender
@ -410,12 +411,7 @@ fun MessageReceiver.handleVisibleMessage(
message.hasMention = listOf(userPublicKey, userBlindedKey) message.hasMention = listOf(userPublicKey, userBlindedKey)
.filterNotNull() .filterNotNull()
.any { key -> .any { key ->
return@any ( messageText?.contains("@$key") == true || key == (quoteModel?.author?.serialize() ?: "")
messageText != null &&
messageText.contains("@$key")
) || (
(quoteModel?.author?.serialize() ?: "") == key
)
} }
// Persist the message // Persist the message

View File

@ -14,7 +14,7 @@ import org.whispersystems.curve25519.Curve25519
import kotlin.experimental.xor import kotlin.experimental.xor
object SodiumUtilities { 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 val curve by lazy { Curve25519.getInstance(Curve25519.BEST) }
private const val SCALAR_LENGTH: Int = 32 // crypto_core_ed25519_scalarbytes private const val SCALAR_LENGTH: Int = 32 // crypto_core_ed25519_scalarbytes

View File

@ -3,8 +3,6 @@
package org.session.libsession.snode package org.session.libsession.snode
import android.os.Build import android.os.Build
import com.goterl.lazysodium.LazySodiumAndroid
import com.goterl.lazysodium.SodiumAndroid
import com.goterl.lazysodium.exceptions.SodiumException import com.goterl.lazysodium.exceptions.SodiumException
import com.goterl.lazysodium.interfaces.GenericHash import com.goterl.lazysodium.interfaces.GenericHash
import com.goterl.lazysodium.interfaces.PwHash import com.goterl.lazysodium.interfaces.PwHash
@ -19,6 +17,7 @@ import nl.komponents.kovenant.functional.map
import nl.komponents.kovenant.task import nl.komponents.kovenant.task
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.utilities.MessageWrapper 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.crypto.getRandomElement
import org.session.libsignal.database.LokiAPIDatabaseProtocol import org.session.libsignal.database.LokiAPIDatabaseProtocol
import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos
@ -41,7 +40,6 @@ import kotlin.collections.set
import kotlin.properties.Delegates.observable import kotlin.properties.Delegates.observable
object SnodeAPI { object SnodeAPI {
private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) }
internal val database: LokiAPIDatabaseProtocol internal val database: LokiAPIDatabaseProtocol
get() = SnodeModule.shared.storage get() = SnodeModule.shared.storage
private val broadcaster: Broadcaster private val broadcaster: Broadcaster

View File

@ -1,23 +1,60 @@
package org.session.libsignal.utilities package org.session.libsignal.utilities
import android.os.Process 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 { object ThreadUtils {
const val TAG = "ThreadUtils"
const val PRIORITY_IMPORTANT_BACKGROUND_THREAD = Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE 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<Runnable>()
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 @JvmStatic
fun queue(target: Runnable) { fun queue(target: Runnable) {
executorPool.execute(target) executorPool.execute {
try {
target.run()
} catch (e: Exception) {
Log.e(TAG, e)
}
}
} }
fun queue(target: () -> Unit) { 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 @JvmStatic
fun newDynamicSingleThreadedExecutor(): ExecutorService { fun newDynamicSingleThreadedExecutor(): ExecutorService {
val executor = ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, LinkedBlockingQueue()) val executor = ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, LinkedBlockingQueue())