fix: fix the placeholder rendering on new messages, add in extra context logging for adding contacts and preventing new thread creation on new messages of various types

This commit is contained in:
0x330a 2023-04-13 17:32:37 +10:00
parent 2ebd6ebf64
commit 3528afa2fb
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C
6 changed files with 179 additions and 86 deletions

View File

@ -3,22 +3,34 @@ package org.thoughtcrime.securesms.conversation.v2
import android.Manifest import android.Manifest
import android.animation.FloatEvaluator import android.animation.FloatEvaluator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.* import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.res.Resources import android.content.res.Resources
import android.database.Cursor import android.database.Cursor
import android.graphics.Rect import android.graphics.Rect
import android.graphics.Typeface import android.graphics.Typeface
import android.net.Uri import android.net.Uri
import android.os.* import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.MediaStore import android.provider.MediaStore
import android.text.SpannableString
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.SpannedString import android.text.SpannedString
import android.text.TextUtils import android.text.TextUtils
import android.text.style.StyleSpan import android.text.style.StyleSpan
import android.util.Pair import android.util.Pair
import android.util.TypedValue import android.util.TypedValue
import android.view.* import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.Toast import android.widget.Toast
@ -72,8 +84,12 @@ import org.session.libsession.messaging.sending_receiving.link_preview.LinkPrevi
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.* import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.Address.Companion.fromSerialized
import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.MediaTypes
import org.session.libsession.utilities.Stub
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.concurrent.SimpleTask import org.session.libsession.utilities.concurrent.SimpleTask
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsession.utilities.recipients.RecipientModifiedListener import org.session.libsession.utilities.recipients.RecipientModifiedListener
@ -106,10 +122,25 @@ import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar
import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel
import org.thoughtcrime.securesms.conversation.v2.utilities.* import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
import org.thoughtcrime.securesms.conversation.v2.utilities.ResendMessageUtilities
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.MnemonicUtilities import org.thoughtcrime.securesms.crypto.MnemonicUtilities
import org.thoughtcrime.securesms.database.* import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.LokiAPIDatabase
import org.thoughtcrime.securesms.database.LokiMessageDatabase
import org.thoughtcrime.securesms.database.LokiThreadDatabase
import org.thoughtcrime.securesms.database.MmsDatabase
import org.thoughtcrime.securesms.database.MmsSmsDatabase
import org.thoughtcrime.securesms.database.ReactionDatabase
import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.database.SessionContactDatabase
import org.thoughtcrime.securesms.database.SmsDatabase
import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
@ -122,13 +153,26 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState
import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaSendActivity import org.thoughtcrime.securesms.mediasend.MediaSendActivity
import org.thoughtcrime.securesms.mms.* import org.thoughtcrime.securesms.mms.AudioSlide
import org.thoughtcrime.securesms.mms.GifSlide
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.ImageSlide
import org.thoughtcrime.securesms.mms.MediaConstraints
import org.thoughtcrime.securesms.mms.Slide
import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.mms.VideoSlide
import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
import org.thoughtcrime.securesms.util.* import org.thoughtcrime.securesms.util.ActivityDispatcher
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.SaveAttachmentTask
import org.thoughtcrime.securesms.util.push
import org.thoughtcrime.securesms.util.toPx
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.* import java.util.Locale
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
@ -455,6 +499,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// TODO: buffer this // TODO: buffer this
bufferedLastSeenChannel.trySend(Unit) bufferedLastSeenChannel.trySend(Unit)
} }
updatePlaceholder()
} }
override fun onLoaderReset(cursor: Loader<Cursor>) { override fun onLoaderReset(cursor: Loader<Cursor>) {

View File

@ -154,18 +154,14 @@ public class RecipientDatabase extends Database {
public Optional<RecipientSettings> getRecipientSettings(@NonNull Address address) { public Optional<RecipientSettings> getRecipientSettings(@NonNull Address address) {
SQLiteDatabase database = databaseHelper.getReadableDatabase(); SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try { try (Cursor cursor = database.query(TABLE_NAME, null, ADDRESS + " = ?", new String[]{address.serialize()}, null, null, null)) {
cursor = database.query(TABLE_NAME, null, ADDRESS + " = ?", new String[] {address.serialize()}, null, null, null);
if (cursor != null && cursor.moveToNext()) { if (cursor != null && cursor.moveToNext()) {
return getRecipientSettings(cursor); return getRecipientSettings(cursor);
} }
return Optional.absent(); return Optional.absent();
} finally {
if (cursor != null) cursor.close();
} }
} }
@ -252,6 +248,16 @@ public class RecipientDatabase extends Database {
notifyRecipientListeners(); notifyRecipientListeners();
} }
public boolean getApproved(@NonNull Address address) {
SQLiteDatabase db = getReadableDatabase();
try (Cursor cursor = db.query(TABLE_NAME, new String[]{APPROVED}, ADDRESS + " = ?", new String[]{address.serialize()}, null, null, null)) {
if (cursor != null && cursor.moveToNext()) {
return cursor.getInt(cursor.getColumnIndexOrThrow(APPROVED)) == 1;
}
}
return false;
}
public void setApproved(@NonNull Recipient recipient, boolean approved) { public void setApproved(@NonNull Recipient recipient, boolean approved) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(APPROVED, approved ? 1 : 0); values.put(APPROVED, approved ? 1 : 0);

View File

@ -98,6 +98,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
// TODO: maybe add time here from formation / creation message // TODO: maybe add time here from formation / creation message
override fun threadCreated(address: Address, threadId: Long) { override fun threadCreated(address: Address, threadId: Long) {
if (!getRecipientApproved(address)) return // don't store unapproved / message requests
Log.d("Loki-DBG", "creating thread for $address\nExecution context:\n${Thread.currentThread().stackTrace.joinToString("\n")}") Log.d("Loki-DBG", "creating thread for $address\nExecution context:\n${Thread.currentThread().stackTrace.joinToString("\n")}")
val volatile = configFactory.convoVolatile ?: return val volatile = configFactory.convoVolatile ?: return
@ -1037,6 +1038,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
override fun setContact(contact: Contact) { override fun setContact(contact: Contact) {
DatabaseComponent.get(context).sessionContactDatabase().setContact(contact) DatabaseComponent.get(context).sessionContactDatabase().setContact(contact)
if (!getRecipientApproved(Address.fromSerialized(contact.sessionID))) return
SSKEnvironment.shared.profileManager.contactUpdatedInternal(contact) SSKEnvironment.shared.profileManager.contactUpdatedInternal(contact)
} }
@ -1050,6 +1052,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
} }
override fun addLibSessionContacts(contacts: List<LibSessionContact>) { override fun addLibSessionContacts(contacts: List<LibSessionContact>) {
Log.d("Loki-DBG", "Adding contacts from execution context:\n${Thread.currentThread().stackTrace.joinToString("\n")}")
val mappingDb = DatabaseComponent.get(context).blindedIdMappingDatabase() val mappingDb = DatabaseComponent.get(context).blindedIdMappingDatabase()
val moreContacts = contacts.filter { contact -> val moreContacts = contacts.filter { contact ->
val id = SessionId(contact.id) val id = SessionId(contact.id)
@ -1197,7 +1200,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
Log.d("Loki-DBG", "Deleting conversation for ${recipient.address}") Log.d("Loki-DBG", "Deleting conversation for ${recipient.address}")
if (recipient.isContactRecipient) { if (recipient.isContactRecipient) {
if (recipient.isLocalNumber) return if (recipient.isLocalNumber) return
// TODO: handle contact
val contacts = configFactory.contacts ?: return val contacts = configFactory.contacts ?: return
contacts.upsertContact(recipient.address.serialize()) { contacts.upsertContact(recipient.address.serialize()) {
this.priority = ConfigBase.PRIORITY_HIDDEN this.priority = ConfigBase.PRIORITY_HIDDEN
@ -1351,6 +1353,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
} }
} }
override fun getRecipientApproved(address: Address): Boolean {
return DatabaseComponent.get(context).recipientDatabase().getApproved(address)
}
override fun setRecipientApproved(recipient: Recipient, approved: Boolean) { override fun setRecipientApproved(recipient: Recipient, approved: Boolean) {
DatabaseComponent.get(context).recipientDatabase().setApproved(recipient, approved) DatabaseComponent.get(context).recipientDatabase().setApproved(recipient, approved)
if (recipient.isLocalNumber || !recipient.isContactRecipient) return if (recipient.isLocalNumber || !recipient.isContactRecipient) return

View File

@ -193,6 +193,7 @@ interface StorageProtocol {
fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long)
fun insertMessageRequestResponse(response: MessageRequestResponse) fun insertMessageRequestResponse(response: MessageRequestResponse)
fun setRecipientApproved(recipient: Recipient, approved: Boolean) fun setRecipientApproved(recipient: Recipient, approved: Boolean)
fun getRecipientApproved(address: Address): Boolean
fun setRecipientApprovedMe(recipient: Recipient, approvedMe: Boolean) fun setRecipientApprovedMe(recipient: Recipient, approvedMe: Boolean)
fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long)
fun conversationHasOutgoing(userPublicKey: String): Boolean fun conversationHasOutgoing(userPublicKey: String): Boolean

View File

@ -10,8 +10,15 @@ import nl.komponents.kovenant.task
import org.session.libsession.database.StorageProtocol import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage
import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.messages.control.DataExtractionNotification
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
import org.session.libsession.messaging.messages.control.MessageRequestResponse
import org.session.libsession.messaging.messages.control.ReadReceipt
import org.session.libsession.messaging.messages.control.SharedConfigurationMessage
import org.session.libsession.messaging.messages.control.TypingIndicator
import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.control.UnsendRequest
import org.session.libsession.messaging.messages.visible.ParsedMessage import org.session.libsession.messaging.messages.visible.ParsedMessage
import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.messages.visible.VisibleMessage
@ -54,6 +61,9 @@ class BatchMessageReceiveJob(
const val BATCH_DEFAULT_NUMBER = 512 const val BATCH_DEFAULT_NUMBER = 512
// used for processing messages that don't have a thread and shouldn't create one
const val NO_THREAD_MAPPING = -1L
// Keys used for database storage // Keys used for database storage
private val NUM_MESSAGES_KEY = "numMessages" private val NUM_MESSAGES_KEY = "numMessages"
private val DATA_KEY = "data" private val DATA_KEY = "data"
@ -64,18 +74,22 @@ class BatchMessageReceiveJob(
private fun shouldCreateThread(parsedMessage: ParsedMessage): Boolean { private fun shouldCreateThread(parsedMessage: ParsedMessage): Boolean {
val message = parsedMessage.message val message = parsedMessage.message
return message is VisibleMessage if (message is VisibleMessage) return true
|| !message.isSenderSelf else { // message is control message otherwise
// TODO: sort out which messages should create threads: message requests? group creation threads? visible messages? others? calls? return when(message) {
is SharedConfigurationMessage -> false
// || (message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) // this was creating threads for self send messages (i.e. synced group creation) is ClosedGroupControlMessage -> message.kind is ClosedGroupControlMessage.Kind.New
// if (message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) { is DataExtractionNotification -> false
// return true is MessageRequestResponse -> false
// } is ExpirationTimerUpdate -> false
// if (parsedMessage.message.isSenderSelf ) { is ConfigurationMessage -> false
// // all the cases where we should add a self send creating the thread, i.e. group invite? visible message? is TypingIndicator -> false
// } is UnsendRequest -> false
// !parsedMessage.message.isSenderSelf || parsedMessage.message is VisibleMessage is ReadReceipt -> false
is CallMessage -> false // TODO: maybe
else -> false // shouldn't happen, or I guess would be Visible
}
}
} }
private fun getThreadId(message: Message, storage: StorageProtocol, shouldCreateThread: Boolean): Long? { private fun getThreadId(message: Message, storage: StorageProtocol, shouldCreateThread: Boolean): Long? {
@ -106,8 +120,8 @@ class BatchMessageReceiveJob(
val (message, proto) = MessageReceiver.parse(data, openGroupMessageServerID, openGroupPublicKey = serverPublicKey) val (message, proto) = MessageReceiver.parse(data, openGroupMessageServerID, openGroupPublicKey = serverPublicKey)
message.serverHash = serverHash message.serverHash = serverHash
val parsedParams = ParsedMessage(messageParameters, message, proto) val parsedParams = ParsedMessage(messageParameters, message, proto)
val threadID = getThreadId(message, storage, shouldCreateThread(parsedParams)) val threadID = getThreadId(message, storage, shouldCreateThread(parsedParams)) ?: NO_THREAD_MAPPING
if (threadID != null && !threadMap.containsKey(threadID)) { if (!threadMap.containsKey(threadID)) {
threadMap[threadID] = mutableListOf(parsedParams) threadMap[threadID] = mutableListOf(parsedParams)
} else { } else {
threadMap[threadID]!! += parsedParams threadMap[threadID]!! += parsedParams
@ -136,8 +150,8 @@ class BatchMessageReceiveJob(
// iterate over threads and persist them (persistence is the longest constant in the batch process operation) // iterate over threads and persist them (persistence is the longest constant in the batch process operation)
runBlocking(Dispatchers.IO) { runBlocking(Dispatchers.IO) {
val deferredThreadMap = threadMap.entries.map { (threadId, messages) ->
async { fun processMessages(threadId: Long, messages: List<ParsedMessage>) = async {
// The LinkedHashMap should preserve insertion order // The LinkedHashMap should preserve insertion order
val messageIds = linkedMapOf<Long, Pair<Boolean, Boolean>>() val messageIds = linkedMapOf<Long, Pair<Boolean, Boolean>>()
val myLastSeen = storage.getLastSeen(threadId) val myLastSeen = storage.getLastSeen(threadId)
@ -146,12 +160,22 @@ class BatchMessageReceiveJob(
try { try {
when (message) { when (message) {
is VisibleMessage -> { is VisibleMessage -> {
val isUserBlindedSender = message.sender == serverPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId( val isUserBlindedSender =
IdPrefix.BLINDED, it.publicKey.asBytes).hexString } message.sender == serverPublicKey?.let {
SodiumUtilities.blindedKeyPair(
it,
MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!
)
}?.let {
SessionId(
IdPrefix.BLINDED, it.publicKey.asBytes
).hexString
}
val sentTimestamp = message.sentTimestamp!! val sentTimestamp = message.sentTimestamp!!
if (message.sender == localUserPublicKey || isUserBlindedSender) { if (message.sender == localUserPublicKey || isUserBlindedSender) {
if (sentTimestamp > newLastSeen) { if (sentTimestamp > newLastSeen) {
newLastSeen = sentTimestamp // use sent timestamp here since that is technically the last one we have newLastSeen =
sentTimestamp // use sent timestamp here since that is technically the last one we have
} }
} }
val messageId = MessageReceiver.handleVisibleMessage( val messageId = MessageReceiver.handleVisibleMessage(
@ -167,12 +191,17 @@ class BatchMessageReceiveJob(
) )
} }
parameters.openGroupMessageServerID?.let { parameters.openGroupMessageServerID?.let {
MessageReceiver.handleOpenGroupReactions(threadId, it, parameters.reactions) MessageReceiver.handleOpenGroupReactions(
threadId,
it,
parameters.reactions
)
} }
} }
is UnsendRequest -> { is UnsendRequest -> {
val deletedMessageId = MessageReceiver.handleUnsendRequest(message) val deletedMessageId =
MessageReceiver.handleUnsendRequest(message)
// If we removed a message then ensure it isn't in the 'messageIds' // If we removed a message then ensure it isn't in the 'messageIds'
if (deletedMessageId != null) { if (deletedMessageId != null) {
@ -203,9 +232,15 @@ class BatchMessageReceiveJob(
storage.updateThread(threadId, true) storage.updateThread(threadId, true)
SSKEnvironment.shared.notificationManager.updateNotification(context, threadId) SSKEnvironment.shared.notificationManager.updateNotification(context, threadId)
} }
val withoutDefault = threadMap.entries.filter { it.key != NO_THREAD_MAPPING }
val noThreadMessages = threadMap[NO_THREAD_MAPPING] ?: listOf()
val deferredThreadMap = withoutDefault.map { (threadId, messages) ->
processMessages(threadId, messages)
} }
// await all thread processing // await all thread processing
deferredThreadMap.awaitAll() deferredThreadMap.awaitAll()
processMessages(NO_THREAD_MAPPING, noThreadMessages)
} }
if (failures.isEmpty()) { if (failures.isEmpty()) {
handleSuccess(dispatcherName) handleSuccess(dispatcherName)