diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt index e88cf1d08b..88f485b3d5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt @@ -7,6 +7,7 @@ import android.view.View import android.widget.LinearLayout import network.loki.messenger.R import network.loki.messenger.databinding.ViewUserBinding +import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities @@ -51,7 +52,7 @@ class UserView : LinearLayout { val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithSessionID(publicKey) return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey } - val threadID = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(user) + val threadID = MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(user.address) MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded(threadID, context) // FIXME: This is a bad place to do this val address = user.address.serialize() binding.profilePictureView.root.glide = glide 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 ae830deb07..f69d19951b 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 @@ -237,7 +237,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe it } val recipient = Recipient.from(this, address, false) - threadId = threadDb.getOrCreateThreadIdFor(recipient) + threadId = storage.getOrCreateThreadIdFor(recipient.address) } } ?: finish() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt index 300217faba..b5a5e24618 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiThreadDatabase.kt @@ -4,11 +4,8 @@ import android.content.ContentValues import android.content.Context import android.database.Cursor import org.session.libsession.messaging.open_groups.OpenGroup -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.JsonUtil import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper -import org.thoughtcrime.securesms.dependencies.DatabaseComponent class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) { @@ -24,12 +21,6 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa val createPublicChatTableCommand = "CREATE TABLE $publicChatTable ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);" } - fun getThreadID(hexEncodedPublicKey: String): Long { - val address = Address.fromSerialized(hexEncodedPublicKey) - val recipient = Recipient.from(context, address, false) - return DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(recipient) - } - fun getAllOpenGroups(): Map { val database = databaseHelper.readableDatabase var cursor: Cursor? = null diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 72ef79f577..1e5e063d55 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -20,13 +20,11 @@ import android.content.ContentValues import android.content.Context import android.database.Cursor import com.annimon.stream.Stream -import com.google.android.mms.pdu_alt.NotificationInd import com.google.android.mms.pdu_alt.PduHeaders import org.json.JSONArray import org.json.JSONException import org.json.JSONObject import org.session.libsession.messaging.messages.signal.IncomingMediaMessage -import org.session.libsession.messaging.messages.signal.OutgoingExpirationUpdateMessage import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage @@ -40,16 +38,13 @@ import org.session.libsession.utilities.Address.Companion.UNKNOWN import org.session.libsession.utilities.Address.Companion.fromExternal import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.Contact -import org.session.libsession.utilities.GroupUtil.doubleEncodeGroupID import org.session.libsession.utilities.IdentityKeyMismatch import org.session.libsession.utilities.IdentityKeyMismatchList import org.session.libsession.utilities.NetworkFailure import org.session.libsession.utilities.NetworkFailureList import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled import org.session.libsession.utilities.Util.toIsoBytes -import org.session.libsession.utilities.Util.toIsoString import org.session.libsession.utilities.recipients.Recipient -import org.session.libsession.utilities.recipients.RecipientFormattingException import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.ThreadUtils.queue @@ -234,34 +229,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa } } - @Throws(RecipientFormattingException::class, MmsException::class) - private fun getThreadIdFor(retrieved: IncomingMediaMessage): Long { - return if (retrieved.groupId != null) { - val groupRecipients = Recipient.from( - context, - retrieved.groupId, - true - ) - get(context).threadDatabase().getOrCreateThreadIdFor(groupRecipients) - } else { - val sender = Recipient.from( - context, - retrieved.from, - true - ) - get(context).threadDatabase().getOrCreateThreadIdFor(sender) - } - } - - private fun getThreadIdFor(notification: NotificationInd): Long { - val fromString = - if (notification.from != null && notification.from.textString != null) toIsoString( - notification.from.textString - ) else "" - val recipient = Recipient.from(context, fromExternal(context, fromString), false) - return get(context).threadDatabase().getOrCreateThreadIdFor(recipient) - } - private fun rawQuery(where: String, arguments: Array?): Cursor { val database = databaseHelper.readableDatabase return database.rawQuery( @@ -272,10 +239,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa ) } - fun getMessages(idsAsString: String): Cursor { - return rawQuery(idsAsString, null) - } - fun getMessage(messageId: Long): Cursor { val cursor = rawQuery(RAW_ID_WHERE, arrayOf(messageId.toString())) setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId)) @@ -633,15 +596,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa serverTimestamp: Long, runThreadUpdate: Boolean ): Optional { - var threadId = threadId - if (threadId == -1L || retrieved.isGroupMessage) { - try { - threadId = getThreadIdFor(retrieved) - } catch (e: RecipientFormattingException) { - Log.w("MmsDatabase", e) - if (threadId == -1L) throw MmsException(e) - } - } + if (threadId < 0 ) throw MmsException("No thread ID supplied!") val contentValues = ContentValues() contentValues.put(DATE_SENT, retrieved.sentTimeMillis) contentValues.put(ADDRESS, retrieved.from.serialize()) @@ -710,27 +665,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa serverTimestamp: Long, runThreadUpdate: Boolean ): Optional { - var threadId = threadId - if (threadId == -1L) { - if (retrieved.isGroup) { - val decodedGroupId: String = if (retrieved is OutgoingExpirationUpdateMessage) { - retrieved.groupId - } else { - (retrieved as OutgoingGroupMediaMessage).groupId - } - val groupId: String - groupId = try { - doubleEncodeGroupID(decodedGroupId) - } catch (e: IOException) { - Log.e(TAG, "Couldn't encrypt group ID") - throw MmsException(e) - } - val group = Recipient.from(context, fromSerialized(groupId), false) - threadId = get(context).threadDatabase().getOrCreateThreadIdFor(group) - } else { - threadId = get(context).threadDatabase().getOrCreateThreadIdFor(retrieved.recipient) - } - } + if (threadId < 0 ) throw MmsException("No thread ID supplied!") val messageId = insertMessageOutbox(retrieved, threadId, false, null, serverTimestamp, runThreadUpdate) if (messageId == -1L) { return Optional.absent() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index f25ed9b934..86a7742710 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -202,13 +202,16 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF } val targetRecipient = Recipient.from(context, targetAddress, false) if (!targetRecipient.isGroupRecipient) { - val recipientDb = DatabaseComponent.get(context).recipientDatabase() if (isUserSender || isUserBlindedSender) { setRecipientApproved(targetRecipient, true) } else { setRecipientApprovedMe(targetRecipient, true) } } + if (message.threadID == null && !targetRecipient.isOpenGroupRecipient) { + // open group recipients should explicitly create threads + message.threadID = getOrCreateThreadIdFor(targetAddress) + } if (message.isMediaMessage() || attachments.isNotEmpty()) { val quote: Optional = if (quotes != null) Optional.of(quotes) else Optional.absent() val linkPreviews: Optional> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! }) @@ -222,7 +225,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF it.toSignalPointer() } val mediaMessage = IncomingMediaMessage.from(message, senderAddress, targetRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews) - mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.receivedTimestamp ?: 0, runThreadUpdate) + mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID!!, message.receivedTimestamp ?: 0, runThreadUpdate) } if (insertResult.isPresent) { messageID = insertResult.get().messageId @@ -998,6 +1001,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN) profileManager.setProfilePictureURL(context, recipient, url) } + if (contact.hidden) { + getThreadId(fromSerialized(contact.id))?.let { conversationThreadId -> + deleteConversation(conversationThreadId) + } + } Log.d("Loki-DBG", "Updated contact $contact") } } @@ -1075,8 +1083,21 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF override fun deleteConversation(threadID: Long) { // TODO: delete from either contacts / convo volatile or the closed groups + val recipient = getRecipientForThread(threadID) val threadDB = DatabaseComponent.get(context).threadDatabase() threadDB.deleteConversation(threadID) + if (recipient != null) { + if (recipient.isContactRecipient) { + // TODO: handle contact + val contacts = configFactory.contacts ?: return + contacts.upsertContact(recipient.address.serialize()) { + this.hidden = true + } + ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context) + } else if (recipient.isClosedGroupRecipient) { + // TODO: handle closed group + } + } } override fun getAttachmentDataUri(attachmentId: AttachmentId): Uri { @@ -1094,6 +1115,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF if (recipient.isBlocked) return + val threadId = getThreadId(recipient) ?: return + val mediaMessage = IncomingMediaMessage( address, sentTimestamp, @@ -1112,7 +1135,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF Optional.of(message) ) - database.insertSecureDecryptedMessageInbox(mediaMessage, -1, runThreadUpdate = true) + database.insertSecureDecryptedMessageInbox(mediaMessage, threadId, runThreadUpdate = true) } override fun insertMessageRequestResponse(response: MessageRequestResponse) { @@ -1131,7 +1154,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF val mmsDb = DatabaseComponent.get(context).mmsDatabase() val smsDb = DatabaseComponent.get(context).smsDatabase() val sender = Recipient.from(context, fromSerialized(senderPublicKey), false) - val threadId = threadDB.getOrCreateThreadIdFor(sender) + val threadId = getOrCreateThreadIdFor(sender.address) val profile = response.profile if (profile != null) { val profileManager = SSKEnvironment.shared.profileManager diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java index 7aa00b7126..532477b646 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.service; import android.content.Context; import org.jetbrains.annotations.NotNull; +import org.session.libsession.database.StorageProtocol; import org.session.libsession.messaging.MessagingModuleConfiguration; import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate; import org.session.libsession.messaging.messages.signal.IncomingMediaMessage; @@ -107,6 +108,10 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM Address groupAddress = Address.fromSerialized(groupID); recipient = Recipient.from(context, groupAddress, false); } + Long threadId = MessagingModuleConfiguration.getShared().getStorage().getThreadId(recipient); + if (threadId == null) { + return; + } IncomingMediaMessage mediaMessage = new IncomingMediaMessage(address, sentTimestamp, -1, duration * 1000L, true, @@ -121,7 +126,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM Optional.absent(), Optional.absent()); //insert the timer update message - database.insertSecureDecryptedMessageInbox(mediaMessage, -1, true); + database.insertSecureDecryptedMessageInbox(mediaMessage, threadId, true); //set the timer to the conversation MessagingModuleConfiguration.getShared().getStorage().setExpirationTimer(recipient.getAddress().serialize(), duration); @@ -140,6 +145,10 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM Address address = Address.fromSerialized((message.getSyncTarget() != null && !message.getSyncTarget().isEmpty()) ? message.getSyncTarget() : message.getRecipient()); Recipient recipient = Recipient.from(context, address, false); + if (message.getThreadID() == null || message.getThreadID() < 0) { + StorageProtocol storage = MessagingModuleConfiguration.getShared().getStorage(); + message.setThreadID(storage.getOrCreateThreadIdFor(address)); + } try { OutgoingExpirationUpdateMessage timerUpdateMessage = new OutgoingExpirationUpdateMessage(recipient, sentTimestamp, duration * 1000L, groupId); diff --git a/libsession-util/src/main/cpp/contacts.h b/libsession-util/src/main/cpp/contacts.h index dbda80fb8f..97f9d96af7 100644 --- a/libsession-util/src/main/cpp/contacts.h +++ b/libsession-util/src/main/cpp/contacts.h @@ -13,17 +13,18 @@ inline session::config::Contacts *ptrToContacts(JNIEnv *env, jobject obj) { inline jobject serialize_contact(JNIEnv *env, session::config::contact_info info) { jclass contactClass = env->FindClass("network/loki/messenger/libsession_util/util/Contact"); - jmethodID constructor = env->GetMethodID(contactClass, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZLnetwork/loki/messenger/libsession_util/util/UserPic;ILnetwork/loki/messenger/libsession_util/util/ExpiryMode;)V"); + jmethodID constructor = env->GetMethodID(contactClass, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZZLnetwork/loki/messenger/libsession_util/util/UserPic;ILnetwork/loki/messenger/libsession_util/util/ExpiryMode;)V"); jstring id = env->NewStringUTF(info.session_id.data()); jstring name = env->NewStringUTF(info.name.data()); jstring nickname = env->NewStringUTF(info.nickname.data()); - jboolean approved, approvedMe, blocked; + jboolean approved, approvedMe, blocked, hidden; approved = info.approved; approvedMe = info.approved_me; blocked = info.blocked; + hidden = info.hidden; jobject profilePic = util::serialize_user_pic(env, info.profile_picture); jobject returnObj = env->NewObject(contactClass, constructor, id, name, nickname, approved, - approvedMe, blocked, profilePic, info.priority, + approvedMe, blocked, hidden, profilePic, info.priority, util::serialize_expiry(env, info.exp_mode, info.exp_timer)); return returnObj; } @@ -32,13 +33,14 @@ inline session::config::contact_info deserialize_contact(JNIEnv *env, jobject info, session::config::Contacts *conf) { jclass contactClass = env->FindClass("network/loki/messenger/libsession_util/util/Contact"); - jfieldID getId, getName, getNick, getApproved, getApprovedMe, getBlocked, getUserPic, getPriority, getExpiry; + jfieldID getId, getName, getNick, getApproved, getApprovedMe, getBlocked, getUserPic, getPriority, getExpiry, getHidden; getId = env->GetFieldID(contactClass, "id", "Ljava/lang/String;"); getName = env->GetFieldID(contactClass, "name", "Ljava/lang/String;"); getNick = env->GetFieldID(contactClass, "nickname", "Ljava/lang/String;"); getApproved = env->GetFieldID(contactClass, "approved", "Z"); getApprovedMe = env->GetFieldID(contactClass, "approvedMe", "Z"); getBlocked = env->GetFieldID(contactClass, "blocked", "Z"); + getHidden = env->GetFieldID(contactClass, "hidden", "Z"); getUserPic = env->GetFieldID(contactClass, "profilePicture", "Lnetwork/loki/messenger/libsession_util/util/UserPic;"); getPriority = env->GetFieldID(contactClass, "priority", "I"); @@ -47,11 +49,12 @@ deserialize_contact(JNIEnv *env, jobject info, session::config::Contacts *conf) session_id = static_cast(env->GetObjectField(info, getId)); name = static_cast(env->GetObjectField(info, getName)); nickname = static_cast(env->GetObjectField(info, getNick)); - bool approved, approvedMe, blocked; + bool approved, approvedMe, blocked, hidden; int priority = env->GetIntField(info, getPriority); approved = env->GetBooleanField(info, getApproved); approvedMe = env->GetBooleanField(info, getApprovedMe); blocked = env->GetBooleanField(info, getBlocked); + hidden = env->GetBooleanField(info, getHidden); jobject user_pic = env->GetObjectField(info, getUserPic); jobject expiry_mode = env->GetObjectField(info, getExpiry); @@ -83,6 +86,7 @@ deserialize_contact(JNIEnv *env, jobject info, session::config::Contacts *conf) contact_info.approved = approved; contact_info.approved_me = approvedMe; contact_info.blocked = blocked; + contact_info.hidden = hidden; if (!url.empty() && !key.empty()) { contact_info.profile_picture = session::config::profile_pic(url, key); } else { diff --git a/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/Contact.kt b/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/Contact.kt index f7f7b0a5b7..6498a6f4a4 100644 --- a/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/Contact.kt +++ b/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/Contact.kt @@ -7,6 +7,7 @@ data class Contact( var approved: Boolean = false, var approvedMe: Boolean = false, var blocked: Boolean = false, + var hidden: Boolean = false, var profilePicture: UserPic = UserPic.DEFAULT, var priority: Int = 0, var expiryMode: ExpiryMode