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 5a13830bd1..e2f78eecc7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -19,6 +19,7 @@ import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.database.AttachmentDatabase import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.database.MessagingDatabase import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.events.PartProgressEvent import org.thoughtcrime.securesms.mms.MediaConstraints @@ -167,14 +168,28 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) } override fun deleteMessage(messageID: Long, isSms: Boolean) { - if (isSms) { - val db = DatabaseFactory.getSmsDatabase(context) - db.deleteMessage(messageID) - } else { - val db = DatabaseFactory.getMmsDatabase(context) - db.delete(messageID) - } + val messagingDatabase: MessagingDatabase = if (isSms) DatabaseFactory.getSmsDatabase(context) + else DatabaseFactory.getMmsDatabase(context) + messagingDatabase.deleteMessage(messageID) DatabaseFactory.getLokiMessageDatabase(context).deleteMessage(messageID, isSms) + DatabaseFactory.getLokiMessageDatabase(context).deleteMessageServerHash(messageID) + } + + override fun updateMessageAsDeleted(timestamp: Long, author: String) { + val database = DatabaseFactory.getMmsSmsDatabase(context) + val address = Address.fromSerialized(author) + val message = database.getMessageFor(timestamp, address) ?: return + val messagingDatabase: MessagingDatabase = if (message.isMms) DatabaseFactory.getMmsDatabase(context) + else DatabaseFactory.getSmsDatabase(context) + messagingDatabase.markAsDeleted(message.id, message.isRead) + if (message.isOutgoing) { + messagingDatabase.deleteMessage(message.id) + } + } + + override fun getServerHashForMessage(messageID: Long): String? { + val messageDB = DatabaseFactory.getLokiMessageDatabase(context) + return messageDB.getMessageServerHash(messageID) } override fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? { 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 4a86d5edc5..b5aa6a1ae3 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 @@ -50,6 +50,7 @@ import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.mentions.Mention import org.session.libsession.messaging.mentions.MentionsManager import org.session.libsession.messaging.messages.control.DataExtractionNotification +import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.visible.OpenGroupInvitation @@ -59,8 +60,10 @@ import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address 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.TextSecurePreferences import org.session.libsession.utilities.concurrent.SimpleTask @@ -70,6 +73,7 @@ import org.session.libsignal.crypto.MnemonicCodec import org.session.libsignal.utilities.ListenableFuture import org.session.libsignal.utilities.guava.Optional import org.session.libsignal.utilities.hexEncodedPrivateKey +import org.session.libsignal.utilities.toHexString import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.audio.AudioRecorder @@ -205,6 +209,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe const val PICK_GIF = 10 const val PICK_FROM_LIBRARY = 12 const val INVITE_CONTACTS = 124 + + //flag + const val IS_UNSEND_REQUESTS_ENABLED = false } // endregion @@ -1114,7 +1121,61 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe stopAudioHandler.removeCallbacks(stopVoiceMessageRecordingTask) } - override fun deleteMessages(messages: Set) { + private fun buildUnsendRequest(message: MessageRecord): UnsendRequest? { + if (this.thread.isOpenGroupRecipient) return null + val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider + messageDataProvider.getServerHashForMessage(message.id) ?: return null + val unsendRequest = UnsendRequest() + if (message.isOutgoing) { + unsendRequest.author = TextSecurePreferences.getLocalNumber(this) + } else { + unsendRequest.author = message.individualRecipient.address.contactIdentifier() + } + unsendRequest.timestamp = message.timestamp + + return unsendRequest + } + + private fun deleteLocally(message: MessageRecord) { + buildUnsendRequest(message)?.let { unsendRequest -> + TextSecurePreferences.getLocalNumber(this@ConversationActivityV2)?.let { + MessageSender.send(unsendRequest, Address.fromSerialized(it)) + } + } + MessagingModuleConfiguration.shared.messageDataProvider.deleteMessage(message.id, !message.isMms) + } + + private fun deleteForEveryone(message: MessageRecord) { + buildUnsendRequest(message)?.let { unsendRequest -> + MessageSender.send(unsendRequest, thread.address) + } + val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider + val messageDB = DatabaseFactory.getLokiMessageDatabase(this@ConversationActivityV2) + val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID) + if (openGroup != null) { + messageDB.getServerID(message.id, !message.isMms)?.let { messageServerID -> + OpenGroupAPIV2.deleteMessage(messageServerID, openGroup.room, openGroup.server) + .success { + messageDataProvider.deleteMessage(message.id, !message.isMms) + }.failUi { error -> + Toast.makeText(this@ConversationActivityV2, "Couldn't delete message due to error: $error", Toast.LENGTH_LONG).show() + } + } + } else { + messageDataProvider.deleteMessage(message.id, !message.isMms) + messageDataProvider.getServerHashForMessage(message.id)?.let { serverHash -> + var publicKey = thread.address.serialize() + if (thread.isClosedGroupRecipient) { publicKey = GroupUtil.doubleDecodeGroupID(publicKey).toHexString() } + SnodeAPI.deleteMessage(publicKey, listOf(serverHash)) + .failUi { error -> + Toast.makeText(this@ConversationActivityV2, "Couldn't delete message due to error: $error", Toast.LENGTH_LONG).show() + } + } + } + } + + // Remove this after the unsend request is enabled + fun deleteMessagesWithoutUnsendRequest(messages: Set) { val messageCount = messages.size val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val messageDB = DatabaseFactory.getLokiMessageDatabase(this@ConversationActivityV2) @@ -1141,7 +1202,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } else { for (message in messages) { if (message.isMms) { - DatabaseFactory.getMmsDatabase(this@ConversationActivityV2).delete(message.id) + DatabaseFactory.getMmsDatabase(this@ConversationActivityV2).deleteMessage(message.id) } else { DatabaseFactory.getSmsDatabase(this@ConversationActivityV2).deleteMessage(message.id) } @@ -1156,6 +1217,72 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe builder.show() } + override fun deleteMessages(messages: Set) { + if (!IS_UNSEND_REQUESTS_ENABLED) { + deleteMessagesWithoutUnsendRequest(messages) + return + } + val allSentByCurrentUser = messages.all { it.isOutgoing } + val allHasHash = messages.all { DatabaseFactory.getLokiMessageDatabase(this@ConversationActivityV2).getMessageServerHash(it.id) != null } + if (thread.isOpenGroupRecipient) { + val messageCount = messages.size + val builder = AlertDialog.Builder(this) + builder.setTitle(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount)) + builder.setMessage(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount)) + builder.setCancelable(true) + builder.setPositiveButton(R.string.delete) { _, _ -> + for (message in messages) { + this.deleteForEveryone(message) + } + endActionMode() + } + builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.dismiss() + endActionMode() + } + builder.show() + } else if (allSentByCurrentUser && allHasHash) { + val bottomSheet = DeleteOptionsBottomSheet() + bottomSheet.recipient = thread + bottomSheet.onDeleteForMeTapped = { + for (message in messages) { + this.deleteLocally(message) + } + bottomSheet.dismiss() + endActionMode() + } + bottomSheet.onDeleteForEveryoneTapped = { + for (message in messages) { + this.deleteForEveryone(message) + } + bottomSheet.dismiss() + endActionMode() + } + bottomSheet.onCancelTapped = { + bottomSheet.dismiss() + endActionMode() + } + bottomSheet.show(supportFragmentManager, bottomSheet.tag) + } else { + val messageCount = messages.size + val builder = AlertDialog.Builder(this) + builder.setTitle(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount)) + builder.setMessage(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount)) + builder.setCancelable(true) + builder.setPositiveButton(R.string.delete) { _, _ -> + for (message in messages) { + this.deleteLocally(message) + } + endActionMode() + } + builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.dismiss() + endActionMode() + } + builder.show() + } + } + override fun banUser(messages: Set) { val builder = AlertDialog.Builder(this) val sessionID = messages.first().individualRecipient.address.toString() 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 15de18a608..59e9f4d6f4 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 @@ -73,9 +73,11 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr val position = viewHolder.adapterPosition view.indexInAdapter = position view.bind(message, getMessageBefore(position, cursor), getMessageAfter(position, cursor), glide, searchQuery) - view.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, view, event) } - view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) } - view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) } + if (!message.isDeleted) { + view.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, view, event) } + view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) } + view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) } + } view.contentViewDelegate = visibleMessageContentViewDelegate } is ControlMessageViewHolder -> viewHolder.view.bind(message) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt new file mode 100644 index 0000000000..4d84f7c0f9 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt @@ -0,0 +1,52 @@ +package org.thoughtcrime.securesms.conversation.v2 + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlinx.android.synthetic.main.fragment_conversation_bottom_sheet.* +import kotlinx.android.synthetic.main.fragment_delete_message_bottom_sheet.* +import network.loki.messenger.R +import org.session.libsession.utilities.recipients.Recipient +import org.thoughtcrime.securesms.util.UiModeUtilities + +class DeleteOptionsBottomSheet: BottomSheetDialogFragment(), View.OnClickListener { + + lateinit var recipient: Recipient + + var onDeleteForMeTapped: (() -> Unit?)? = null + var onDeleteForEveryoneTapped: (() -> Unit)? = null + var onCancelTapped: (() -> Unit)? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_delete_message_bottom_sheet, container, false) + } + + override fun onClick(v: View?) { + when (v) { + deleteForMeTextView -> onDeleteForMeTapped?.invoke() + deleteForEveryoneTextView -> onDeleteForEveryoneTapped?.invoke() + cancelTextView -> onCancelTapped?.invoke() + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + if (!this::recipient.isInitialized) { return dismiss() } + if (!recipient.isGroupRecipient) { + deleteForEveryoneTextView.text = resources.getString(R.string.delete_message_for_me_and_recipient, recipient.name) + } + deleteForMeTextView.setOnClickListener(this) + deleteForEveryoneTextView.setOnClickListener(this) + cancelTextView.setOnClickListener(this) + } + + override fun onStart() { + super.onStart() + val window = dialog?.window ?: return + val isLightMode = UiModeUtilities.isDayUiMode(requireContext()) + window.setDimAmount(if (isLightMode) 0.1f else 0.75f) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt index 2372657751..48772f8adf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt @@ -7,6 +7,7 @@ import android.view.MenuItem import network.loki.messenger.R import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.utilities.TextSecurePreferences +import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.ConversationAdapter import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord @@ -34,8 +35,17 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p val thread = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadID)!! val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! fun userCanDeleteSelectedItems(): Boolean { - if (openGroup == null) { return true } val allSentByCurrentUser = selectedItems.all { it.isOutgoing } + + // Remove this after the unsend request is enabled + if (!ConversationActivityV2.IS_UNSEND_REQUESTS_ENABLED) { + if (openGroup == null) { return true } + if (allSentByCurrentUser) { return true } + return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server) + } + + val allReceivedByCurrentUser = selectedItems.all { !it.isOutgoing } + if (openGroup == null) { return allSentByCurrentUser || allReceivedByCurrentUser } if (allSentByCurrentUser) { return true } return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt new file mode 100644 index 0000000000..a91473352c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/DeletedMessageView.kt @@ -0,0 +1,37 @@ +package org.thoughtcrime.securesms.conversation.v2.messages + +import android.content.Context +import android.content.res.ColorStateList +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.LinearLayout +import androidx.annotation.ColorInt +import kotlinx.android.synthetic.main.fragment_conversation_bottom_sheet.view.* +import kotlinx.android.synthetic.main.view_deleted_message.view.* +import kotlinx.android.synthetic.main.view_document.view.* +import network.loki.messenger.R +import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.database.model.MmsMessageRecord +import java.util.* + +class DeletedMessageView : LinearLayout { + + // region Lifecycle + constructor(context: Context) : super(context) { initialize() } + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } + + private fun initialize() { + LayoutInflater.from(context).inflate(R.layout.view_deleted_message, this) + } + // endregion + + // region Updating + fun bind(message: MessageRecord, @ColorInt textColor: Int) { + assert(message.isDeleted) + deleteTitleTextView.text = context.getString(R.string.deleted_message) + deleteTitleTextView.setTextColor(textColor) + deletedMessageViewIconImageView.imageTintList = ColorStateList.valueOf(textColor) + } + // endregion +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 8db00031f9..38831ed5ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -77,7 +77,11 @@ class VisibleMessageContentView : LinearLayout { mainContainer.removeAllViews() onContentClick = null onContentDoubleTap = null - if (message is MmsMessageRecord && message.linkPreviews.isNotEmpty()) { + if (message.isDeleted) { + val deletedMessageView = DeletedMessageView(context) + deletedMessageView.bind(message, VisibleMessageContentView.getTextColor(context,message)) + mainContainer.addView(deletedMessageView) + } else if (message is MmsMessageRecord && message.linkPreviews.isNotEmpty()) { val linkPreviewView = LinkPreviewView(context) linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster, searchQuery) mainContainer.addView(linkPreviewView) 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 ad1864d055..5e5b1da220 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 @@ -266,6 +266,7 @@ class VisibleMessageView : LinearLayout { // region Interaction override fun onTouchEvent(event: MotionEvent): Boolean { + if (onPress == null || onSwipeToReply == null || onLongPress == null) { return false } when (event.action) { MotionEvent.ACTION_DOWN -> onDown(event) MotionEvent.ACTION_MOVE -> onMove(event) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt index c36c197cbb..07513ec328 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt @@ -12,12 +12,14 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab private val messageIDTable = "loki_message_friend_request_database" private val messageThreadMappingTable = "loki_message_thread_mapping_database" private val errorMessageTable = "loki_error_message_database" + private val messageHashTable = "loki_message_hash_database" private val messageID = "message_id" private val serverID = "server_id" private val friendRequestStatus = "friend_request_status" private val threadID = "thread_id" private val errorMessage = "error_message" private val messageType = "message_type" + private val serverHash = "server_hash" @JvmStatic val createMessageIDTableCommand = "CREATE TABLE $messageIDTable ($messageID INTEGER PRIMARY KEY, $serverID INTEGER DEFAULT 0, $friendRequestStatus INTEGER DEFAULT 0);" @JvmStatic @@ -28,6 +30,8 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab val updateMessageIDTableForType = "ALTER TABLE $messageIDTable ADD COLUMN $messageType INTEGER DEFAULT 0; ALTER TABLE $messageIDTable ADD CONSTRAINT PK_$messageIDTable PRIMARY KEY ($messageID, $serverID);" @JvmStatic val updateMessageMappingTable = "ALTER TABLE $messageThreadMappingTable ADD COLUMN $serverID INTEGER DEFAULT 0; ALTER TABLE $messageThreadMappingTable ADD CONSTRAINT PK_$messageThreadMappingTable PRIMARY KEY ($messageID, $serverID);" + @JvmStatic + val createMessageHashTableCommand = "CREATE TABLE IF NOT EXISTS $messageHashTable ($messageID INTEGER PRIMARY KEY, $serverHash STRING);" const val SMS_TYPE = 0 const val MMS_TYPE = 1 @@ -150,4 +154,24 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab database.endTransaction() } } + + fun getMessageServerHash(messageID: Long): String? { + val database = databaseHelper.readableDatabase + return database.get(messageHashTable, "${Companion.messageID} = ?", arrayOf(messageID.toString())) { cursor -> + cursor.getString(serverHash) + } + } + + fun setMessageServerHash(messageID: Long, serverHash: String) { + val database = databaseHelper.writableDatabase + val contentValues = ContentValues(2) + contentValues.put(Companion.messageID, messageID) + contentValues.put(Companion.serverHash, serverHash) + database.insertOrUpdate(messageHashTable, contentValues, "${Companion.messageID} = ?", arrayOf(messageID.toString())) + } + + fun deleteMessageServerHash(messageID: Long) { + val database = databaseHelper.writableDatabase + database.delete(messageHashTable, "${Companion.messageID} = ?", arrayOf(messageID.toString())) + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java index 0fcb61d741..a3906d2871 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java @@ -38,6 +38,10 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn public abstract void markAsSent(long messageId, boolean secure); public abstract void markUnidentified(long messageId, boolean unidentified); + public abstract void markAsDeleted(long messageId, boolean read); + + public abstract boolean deleteMessage(long messageId); + public void addMismatchedIdentity(long messageId, Address address, IdentityKey identityKey) { try { addToDocument(messageId, MISMATCHED_IDENTITIES, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 38e9eeccce..f56792aa71 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -391,6 +391,23 @@ public class MmsDatabase extends MessagingDatabase { db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); } + @Override + public void markAsDeleted(long messageId, boolean read) { + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + ContentValues contentValues = new ContentValues(); + contentValues.put(READ, 1); + contentValues.put(BODY, ""); + database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); + + AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context); + ThreadUtils.queue(() -> attachmentDatabase.deleteAttachmentsForMessage(messageId)); + + long threadId = getThreadIdForMessage(messageId); + if (!read) { DatabaseFactory.getThreadDatabase(context).decrementUnread(threadId, 1); } + updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE, Optional.of(threadId)); + notifyConversationListeners(threadId); + } + @Override public void markExpireStarted(long messageId) { markExpireStarted(messageId, System.currentTimeMillis()); @@ -906,7 +923,8 @@ public class MmsDatabase extends MessagingDatabase { reader.close(); } - public boolean delete(long messageId) { + @Override + public boolean deleteMessage(long messageId) { long threadId = getThreadIdForMessage(messageId); AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context); ThreadUtils.queue(() -> attachmentDatabase.deleteAttachmentsForMessage(messageId)); @@ -1033,7 +1051,7 @@ public class MmsDatabase extends MessagingDatabase { cursor = db.query(TABLE_NAME, new String[] {ID}, where, null, null, null, null); while (cursor != null && cursor.moveToNext()) { - delete(cursor.getLong(0)); + deleteMessage(cursor.getLong(0)); } } finally { @@ -1059,7 +1077,7 @@ public class MmsDatabase extends MessagingDatabase { while (cursor != null && cursor.moveToNext()) { Log.i("MmsDatabase", "Trimming: " + cursor.getLong(0)); - delete(cursor.getLong(0)); + deleteMessage(cursor.getLong(0)); } } finally { 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 81ec17bd0e..52642b5d1f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -40,6 +40,7 @@ public interface MmsSmsColumns { protected static final long BASE_PENDING_SECURE_SMS_FALLBACK = 25; protected static final long BASE_PENDING_INSECURE_SMS_FALLBACK = 26; public static final long BASE_DRAFT_TYPE = 27; + protected static final long BASE_DELETED_TYPE = 28; protected static final long[] OUTGOING_MESSAGE_TYPES = {BASE_OUTBOX_TYPE, BASE_SENT_TYPE, BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE, @@ -152,6 +153,8 @@ public interface MmsSmsColumns { return (type & BASE_TYPE_MASK) == BASE_INBOX_TYPE; } + public static boolean isDeletedMessage(long type) { return (type & BASE_TYPE_MASK) == BASE_DELETED_TYPE; } + public static boolean isJoinedType(long type) { return (type & BASE_TYPE_MASK) == JOINED_TYPE; } 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 42284a4ff0..86b5231643 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -129,7 +129,7 @@ public class MmsSmsDatabase extends Database { String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; - return queryTables(PROJECTION, selection, order, "1"); + return queryTables(PROJECTION, selection, order, null); } public long getLastMessageID(long threadId) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index c148f8ebcc..17a8364776 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -183,6 +183,18 @@ public class SmsDatabase extends MessagingDatabase { db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)}); } + @Override + public void markAsDeleted(long messageId, boolean read) { + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + ContentValues contentValues = new ContentValues(); + contentValues.put(READ, 1); + contentValues.put(BODY, ""); + database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); + long threadId = getThreadIdForMessage(messageId); + if (!read) { DatabaseFactory.getThreadDatabase(context).decrementUnread(threadId, 1); } + updateTypeBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE); + } + @Override public void markExpireStarted(long id) { markExpireStarted(id, System.currentTimeMillis()); @@ -517,6 +529,7 @@ public class SmsDatabase extends MessagingDatabase { return cursor; } + @Override public boolean deleteMessage(long messageId) { Log.i("MessageDatabase", "Deleting: " + messageId); SQLiteDatabase db = databaseHelper.getWritableDatabase(); 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 fe9327a13e..950b6d8238 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -148,6 +148,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, if (openGroupID.isNullOrEmpty() && threadID != null && threadID >= 0) { JobQueue.shared.add(TrimThreadJob(threadID)) } + message.serverHash?.let { serverHash -> + messageID?.let { id -> + DatabaseFactory.getLokiMessageDatabase(context).setMessageServerHash(id, serverHash) + } + } return messageID } @@ -358,6 +363,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } } + override fun setMessageServerHash(messageID: Long, serverHash: String) { + DatabaseFactory.getLokiMessageDatabase(context).setMessageServerHash(messageID, serverHash) + } + override fun getGroup(groupID: String): GroupRecord? { val group = DatabaseFactory.getGroupDatabase(context).getGroup(groupID) return if (group.isPresent) { group.get() } else null diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index c9fa010e8b..361d1449e1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -294,6 +294,14 @@ public class ThreadDatabase extends Database { String.valueOf(threadId)}); } + public void decrementUnread(long threadId, int amount) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " + + UNREAD_COUNT + " = " + UNREAD_COUNT + " - ? WHERE " + ID + " = ? AND " + UNREAD_COUNT + " > 0", + new String[] {String.valueOf(amount), + String.valueOf(threadId)}); + } + public void setDistributionType(long threadId, int distributionType) { ContentValues contentValues = new ContentValues(1); contentValues.put(TYPE, distributionType); @@ -536,9 +544,14 @@ public class ThreadDatabase extends Database { try { reader = mmsSmsDatabase.readerFor(mmsSmsDatabase.getConversationSnippet(threadId)); - MessageRecord record; - - if (reader != null && (record = reader.getNext()) != null) { + MessageRecord record = null; + if (reader != null) { + record = reader.getNext(); + while (record != null && record.isDeleted()) { + record = reader.getNext(); + } + } + if (record != null && !record.isDeleted()) { updateThread(threadId, count, getFormattedBodyFor(record), getAttachmentUriFor(record), record.getTimestamp(), record.getDeliveryStatus(), record.getDeliveryReceiptCount(), record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 106528ee25..ccf5c9e777 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -59,9 +59,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV25 = 46; private static final int lokiV26 = 47; private static final int lokiV27 = 48; + private static final int lokiV28 = 49; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV27; + private static final int DATABASE_VERSION = lokiV28; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -123,6 +124,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand()); db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand()); db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand()); + db.execSQL(LokiMessageDatabase.getCreateMessageHashTableCommand()); db.execSQL(LokiThreadDatabase.getCreateSessionResetTableCommand()); db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand()); db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand()); @@ -302,6 +304,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(RecipientDatabase.getCreateNotificationTypeCommand()); } + if (oldVersion < lokiV28) { + db.execSQL(LokiMessageDatabase.getCreateMessageHashTableCommand()); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index 3adb9cbda5..10e4cb753e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -117,6 +117,7 @@ public abstract class DisplayRecord { public boolean isMissedCall() { return SmsDatabase.Types.isMissedCall(type); } + public boolean isDeleted() { return MmsSmsColumns.Types.isDeletedMessage(type); } public boolean isControlMessage() { return isGroupUpdateMessage() || isExpirationTimerUpdate() || isDataExtractionNotification(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt index afd4ad74a8..d6b1267528 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt @@ -49,9 +49,9 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor // DMs val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! val dmsPromise = SnodeAPI.getMessages(userPublicKey).map { envelopes -> - envelopes.map { envelope -> + envelopes.map { (envelope, serverHash) -> // FIXME: Using a job here seems like a bad idea... - MessageReceiveJob(envelope.toByteArray()).executeAsync() + MessageReceiveJob(envelope.toByteArray(), serverHash).executeAsync() } } promises.addAll(dmsPromise.get()) 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 c2c4834cd5..59cd5cc0c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -229,7 +229,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM } if (expiredMessage != null) { - if (expiredMessage.mms) mmsDatabase.delete(expiredMessage.id); + if (expiredMessage.mms) mmsDatabase.deleteMessage(expiredMessage.id); else smsDatabase.deleteMessage(expiredMessage.id); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AttachmentUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/AttachmentUtil.java index 13cdcc6d6a..2697a634cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/AttachmentUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AttachmentUtil.java @@ -66,7 +66,7 @@ public class AttachmentUtil { .size(); if (attachmentCount <= 1) { - DatabaseFactory.getMmsDatabase(context).delete(mmsId); + DatabaseFactory.getMmsDatabase(context).deleteMessage(mmsId); } else { DatabaseFactory.getAttachmentDatabase(context).deleteAttachment(attachmentId); } diff --git a/app/src/main/res/layout/fragment_delete_message_bottom_sheet.xml b/app/src/main/res/layout/fragment_delete_message_bottom_sheet.xml new file mode 100644 index 0000000000..031813a7c5 --- /dev/null +++ b/app/src/main/res/layout/fragment_delete_message_bottom_sheet.xml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/view_deleted_message.xml b/app/src/main/res/layout/view_deleted_message.xml new file mode 100644 index 0000000000..aed7602591 --- /dev/null +++ b/app/src/main/res/layout/view_deleted_message.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9fa0283acc..360bd4bece 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -893,4 +893,9 @@ Send All Mentions + This message has been deleted + Delete just for me + Delete for everyone + Delete for me and %s + diff --git a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt index 94f59c4b43..356307d1dd 100644 --- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -13,6 +13,8 @@ interface MessageDataProvider { fun getMessageID(serverID: Long): Long? fun getMessageID(serverId: Long, threadId: Long): Pair? fun deleteMessage(messageID: Long, isSms: Boolean) + fun updateMessageAsDeleted(timestamp: Long, author: String) + fun getServerHashForMessage(messageID: Long): String? fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? fun getAttachmentPointer(attachmentId: Long): SessionServiceAttachmentPointer? diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index bc7324d21a..3c8e0c7123 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -93,6 +93,7 @@ interface StorageProtocol { fun markAsSent(timestamp: Long, author: String) fun markUnidentified(timestamp: Long, author: String) fun setErrorMessage(timestamp: Long, author: String, error: Exception) + fun setMessageServerHash(messageID: Long, serverHash: String) // Closed Groups fun getGroup(groupID: String): GroupRecord? diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt index 923e48a83f..0601c670af 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt @@ -7,7 +7,7 @@ import org.session.libsession.messaging.sending_receiving.handle import org.session.libsession.messaging.utilities.Data import org.session.libsignal.utilities.Log -class MessageReceiveJob(val data: ByteArray, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job { +class MessageReceiveJob(val data: ByteArray, val serverHash: String? = null, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job { override var delegate: JobDelegate? = null override var id: String? = null override var failureCount: Int = 0 @@ -21,6 +21,7 @@ class MessageReceiveJob(val data: ByteArray, val openGroupMessageServerID: Long? // Keys used for database storage private val DATA_KEY = "data" + private val SERVER_HASH_KEY = "serverHash" private val OPEN_GROUP_MESSAGE_SERVER_ID_KEY = "openGroupMessageServerID" private val OPEN_GROUP_ID_KEY = "open_group_id" } @@ -34,6 +35,7 @@ class MessageReceiveJob(val data: ByteArray, val openGroupMessageServerID: Long? try { val isRetry: Boolean = failureCount != 0 val (message, proto) = MessageReceiver.parse(this.data, this.openGroupMessageServerID) + message.serverHash = serverHash synchronized(RECEIVE_LOCK) { // FIXME: Do we need this? MessageReceiver.handle(message, proto, this.openGroupID) } @@ -67,6 +69,7 @@ class MessageReceiveJob(val data: ByteArray, val openGroupMessageServerID: Long? override fun serialize(): Data { val builder = Data.Builder().putByteArray(DATA_KEY, data) + serverHash?.let { builder.putString(SERVER_HASH_KEY, it) } openGroupMessageServerID?.let { builder.putLong(OPEN_GROUP_MESSAGE_SERVER_ID_KEY, it) } openGroupID?.let { builder.putString(OPEN_GROUP_ID_KEY, it) } return builder.build(); @@ -81,6 +84,7 @@ class MessageReceiveJob(val data: ByteArray, val openGroupMessageServerID: Long? override fun create(data: Data): MessageReceiveJob { return MessageReceiveJob( data.getByteArray(DATA_KEY), + data.getString(SERVER_HASH_KEY), data.getLong(OPEN_GROUP_MESSAGE_SERVER_ID_KEY), data.getString(OPEN_GROUP_ID_KEY) ) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt index 56779795ea..d201daa98d 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt @@ -13,6 +13,7 @@ abstract class Message { var sender: String? = null var groupPublicKey: String? = null var openGroupServerMessageID: Long? = null + var serverHash: String? = null open val ttl: Long = 14 * 24 * 60 * 60 * 1000 open val isSelfSendValid: Boolean = false diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt new file mode 100644 index 0000000000..a22dd93348 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/UnsendRequest.kt @@ -0,0 +1,55 @@ +package org.session.libsession.messaging.messages.control + +import org.session.libsignal.protos.SignalServiceProtos +import org.session.libsignal.utilities.Log + +class UnsendRequest(): ControlMessage() { + var timestamp: Long? = null + var author: String? = null + + override val isSelfSendValid: Boolean = true + + // region Validation + override fun isValid(): Boolean { + if (!super.isValid()) return false + return timestamp != null && author != null + } + // endregion + + companion object { + const val TAG = "UnsendRequest" + + fun fromProto(proto: SignalServiceProtos.Content): UnsendRequest? { + val unsendRequestProto = if (proto.hasUnsendRequest()) proto.unsendRequest else return null + val timestamp = unsendRequestProto.timestamp + val author = unsendRequestProto.author + return UnsendRequest(timestamp, author) + } + } + + constructor(timestamp: Long, author: String) : this() { + this.timestamp = timestamp + this.author = author + } + + override fun toProto(): SignalServiceProtos.Content? { + val timestamp = timestamp + val author = author + if (timestamp == null || author == null) { + Log.w(TAG, "Couldn't construct unsend request proto from: $this") + return null + } + val unsendRequestProto = SignalServiceProtos.UnsendRequest.newBuilder() + unsendRequestProto.timestamp = timestamp + unsendRequestProto.author = author + val contentProto = SignalServiceProtos.Content.newBuilder() + try { + contentProto.unsendRequest = unsendRequestProto.build() + return contentProto.build() + } catch (e: Exception) { + Log.w(TAG, "Couldn't construct unsend request proto from: $this") + return null + } + } + +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index 99ad38bd5c..0a00892107 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -94,6 +94,7 @@ object MessageReceiver { DataExtractionNotification.fromProto(proto) ?: ExpirationTimerUpdate.fromProto(proto) ?: ConfigurationMessage.fromProto(proto) ?: + UnsendRequest.fromProto(proto) ?: VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage // Ignore self send if needed if (!message.isSelfSendValid && sender == userPublicKey) throw Error.SelfSend 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 4758525abb..1cb041e915 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 @@ -11,6 +11,7 @@ import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate +import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.visible.* import org.session.libsession.messaging.open_groups.* import org.session.libsession.messaging.utilities.MessageWrapper @@ -94,7 +95,7 @@ object MessageSender { // • a closed group control message of type `new` var isNewClosedGroupControlMessage = false if (message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) isNewClosedGroupControlMessage = true - if (isSelfSend && message !is ConfigurationMessage && !isSyncMessage && !isNewClosedGroupControlMessage) { + if (isSelfSend && message !is ConfigurationMessage && !isSyncMessage && !isNewClosedGroupControlMessage && message !is UnsendRequest) { handleSuccessfulMessageSend(message, destination) deferred.resolve(Unit) return promise @@ -161,8 +162,10 @@ object MessageSender { if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { SnodeModule.shared.broadcaster.broadcast("messageSent", message.sentTimestamp!!) } + val hash = it["hash"] as? String + message.serverHash = hash handleSuccessfulMessageSend(message, destination, isSyncMessage) - var shouldNotify = (message is VisibleMessage && !isSyncMessage) + var shouldNotify = ((message is VisibleMessage || message is UnsendRequest) && !isSyncMessage) /* if (message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) { shouldNotify = true @@ -251,28 +254,35 @@ object MessageSender { fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) { val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! - val messageID = storage.getMessageIdInDatabase(message.sentTimestamp!!, message.sender?:userPublicKey) ?: return // Ignore future self-sends storage.addReceivedMessageTimestamp(message.sentTimestamp!!) - if (openGroupSentTimestamp != -1L && message is VisibleMessage) { - storage.addReceivedMessageTimestamp(openGroupSentTimestamp) - storage.updateSentTimestamp(messageID, message.isMediaMessage(), openGroupSentTimestamp, message.threadID!!) - message.sentTimestamp = openGroupSentTimestamp - } - // Track the open group server message ID - if (message.openGroupServerMessageID != null && destination is Destination.OpenGroupV2) { - val encoded = GroupUtil.getEncodedOpenGroupID("${destination.server}.${destination.room}".toByteArray()) - val threadID = storage.getThreadId(Address.fromSerialized(encoded)) - if (threadID != null && threadID >= 0) { - storage.setOpenGroupServerMessageID(messageID, message.openGroupServerMessageID!!, threadID, !(message as VisibleMessage).isMediaMessage()) + storage.getMessageIdInDatabase(message.sentTimestamp!!, message.sender?:userPublicKey)?.let { messageID -> + if (openGroupSentTimestamp != -1L && message is VisibleMessage) { + storage.addReceivedMessageTimestamp(openGroupSentTimestamp) + storage.updateSentTimestamp(messageID, message.isMediaMessage(), openGroupSentTimestamp, message.threadID!!) + message.sentTimestamp = openGroupSentTimestamp + } + // When the sync message is successfully sent, the hash value of this TSOutgoingMessage + // will be replaced by the hash value of the sync message. Since the hash value of the + // real message has no use when we delete a message. It is OK to let it be. + message.serverHash?.let { + storage.setMessageServerHash(messageID, it) + } + // Track the open group server message ID + if (message.openGroupServerMessageID != null && destination is Destination.OpenGroupV2) { + val encoded = GroupUtil.getEncodedOpenGroupID("${destination.server}.${destination.room}".toByteArray()) + val threadID = storage.getThreadId(Address.fromSerialized(encoded)) + if (threadID != null && threadID >= 0) { + storage.setOpenGroupServerMessageID(messageID, message.openGroupServerMessageID!!, threadID, !(message as VisibleMessage).isMediaMessage()) + } + } + // Mark the message as sent + storage.markAsSent(message.sentTimestamp!!, message.sender?:userPublicKey) + storage.markUnidentified(message.sentTimestamp!!, message.sender?:userPublicKey) + // Start the disappearing messages timer if needed + if (message is VisibleMessage && !isSyncMessage) { + SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message.sentTimestamp!!, message.sender?:userPublicKey) } - } - // Mark the message as sent - storage.markAsSent(message.sentTimestamp!!, message.sender?:userPublicKey) - storage.markUnidentified(message.sentTimestamp!!, message.sender?:userPublicKey) - // Start the disappearing messages timer if needed - if (message is VisibleMessage && !isSyncMessage) { - SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(message.sentTimestamp!!, message.sender?:userPublicKey) } // Sync the message if: // • it's a visible message 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 cfbd5605bd..de2b8a349c 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 @@ -14,6 +14,7 @@ import org.session.libsession.messaging.sending_receiving.link_preview.LinkPrevi import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2 import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.recipients.Recipient @@ -49,6 +50,7 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message) is DataExtractionNotification -> handleDataExtractionNotification(message) is ConfigurationMessage -> handleConfigurationMessage(message) + is UnsendRequest -> handleUnsendRequest(message) is VisibleMessage -> handleVisibleMessage(message, proto, openGroupID) } } @@ -145,6 +147,23 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) { } storage.addContacts(message.contacts) } + +fun MessageReceiver.handleUnsendRequest(message: UnsendRequest) { + if (message.sender != message.author) { return } + val context = MessagingModuleConfiguration.shared.context + val storage = MessagingModuleConfiguration.shared.storage + val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider + val timestamp = message.timestamp ?: return + val author = message.author ?: return + val messageIdToDelete = storage.getMessageIdInDatabase(timestamp, author) ?: return + messageDataProvider.getServerHashForMessage(messageIdToDelete)?.let { serverHash -> + SnodeAPI.deleteMessage(author, listOf(serverHash)) + } + messageDataProvider.updateMessageAsDeleted(timestamp, author) + if (!messageDataProvider.isOutgoingMessage(messageIdToDelete)) { + SSKEnvironment.shared.notificationManager.updateNotification(context) + } +} //endregion // region Visible Messages diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPollerV2.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPollerV2.kt index 62e39b40f1..f165c97540 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPollerV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPollerV2.kt @@ -102,8 +102,8 @@ class ClosedGroupPollerV2 { } promise.success { envelopes -> if (!isPolling(groupPublicKey)) { return@success } - envelopes.forEach { envelope -> - val job = MessageReceiveJob(envelope.toByteArray()) + envelopes.forEach { (envelope, serverHash) -> + val job = MessageReceiveJob(envelope.toByteArray(), serverHash) JobQueue.shared.add(job) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt index bbf2620f97..be472874d0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt @@ -91,8 +91,8 @@ class Poller { task { Unit } // The long polling connection has been canceled; don't recurse } else { val messages = SnodeAPI.parseRawMessagesResponse(rawResponse, snode, userPublicKey) - messages.forEach { envelope -> - val job = MessageReceiveJob(envelope.toByteArray()) + messages.forEach { (envelope, serverHash) -> + val job = MessageReceiveJob(envelope.toByteArray(), serverHash) JobQueue.shared.add(job) } poll(snode, deferred) 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 4da3409d04..dfbc6c34da 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -52,7 +52,7 @@ object SnodeAPI { if (useTestnet) { setOf( "http://public.loki.foundation:38157" ) } else { - setOf( "https://storage.seed1.loki.network:$seedNodePort ", "https://storage.seed3.loki.network:$seedNodePort ", "https://public.loki.foundation:$seedNodePort" ) + setOf( "https://storage.seed1.loki.network:$seedNodePort", "https://storage.seed3.loki.network:$seedNodePort", "https://public.loki.foundation:$seedNodePort" ) } } private val snodeFailureThreshold = 3 @@ -329,6 +329,50 @@ object SnodeAPI { } } + fun deleteMessage(publicKey: String, serverHashes: List): Promise, Exception> { + return retryIfNeeded(maxRetryCount) { + val module = MessagingModuleConfiguration.shared + val userED25519KeyPair = module.getUserED25519KeyPair() ?: return@retryIfNeeded Promise.ofFail(Error.NoKeyPair) + val userPublicKey = module.storage.getUserPublicKey() ?: return@retryIfNeeded Promise.ofFail(Error.NoKeyPair) + getSingleTargetSnode(publicKey).bind { snode -> + retryIfNeeded(maxRetryCount) { + val signature = ByteArray(Sign.BYTES) + val verificationData = (Snode.Method.DeleteMessage.rawValue + serverHashes.fold("") { a, v -> a + v }).toByteArray() + sodium.cryptoSignDetached(signature, verificationData, verificationData.size.toLong(), userED25519KeyPair.secretKey.asBytes) + val deleteMessageParams = mapOf( + "pubkey" to userPublicKey, + "pubkey_ed25519" to userED25519KeyPair.publicKey.asHexString, + "messages" to serverHashes, + "signature" to Base64.encodeBytes(signature) + ) + invoke(Snode.Method.DeleteMessage, snode, publicKey, deleteMessageParams).map { rawResponse -> + val swarms = rawResponse["swarm"] as? Map ?: return@map mapOf() + val result = swarms.mapNotNull { (hexSnodePublicKey, rawJSON) -> + val json = rawJSON as? Map ?: return@mapNotNull null + val isFailed = json["failed"] as? Boolean ?: false + val statusCode = json["code"] as? String + val reason = json["reason"] as? String + hexSnodePublicKey to if (isFailed) { + Log.e("Loki", "Failed to delete messages from: $hexSnodePublicKey due to error: $reason ($statusCode).") + false + } else { + val hashes = json["deleted"] as List // Hashes of deleted messages + val signature = json["signature"] as String + val snodePublicKey = Key.fromHexString(hexSnodePublicKey) + // The signature looks like ( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] ) + val message = (userPublicKey + serverHashes.fold("") { a, v -> a + v } + hashes.fold("") { a, v -> a + v }).toByteArray() + sodium.cryptoSignVerifyDetached(Base64.decode(signature), message, message.size, snodePublicKey.asBytes) + } + } + return@map result.toMap() + }.fail { e -> + Log.e("Loki", "Failed to delete messages", e) + } + } + } + } + } + // Parsing private fun parseSnodes(rawResponse: Any): List { val json = rawResponse as? Map<*, *> @@ -382,7 +426,7 @@ object SnodeAPI { } } - fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String): List { + fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String): List> { val messages = rawResponse["messages"] as? List<*> return if (messages != null) { updateLastMessageHashValueIfPossible(snode, publicKey, messages) @@ -421,14 +465,14 @@ object SnodeAPI { return result } - private fun parseEnvelopes(rawMessages: List<*>): List { + private fun parseEnvelopes(rawMessages: List<*>): List> { return rawMessages.mapNotNull { rawMessage -> val rawMessageAsJSON = rawMessage as? Map<*, *> val base64EncodedData = rawMessageAsJSON?.get("data") as? String val data = base64EncodedData?.let { Base64.decode(it) } if (data != null) { try { - MessageWrapper.unwrap(data) + Pair(MessageWrapper.unwrap(data), rawMessageAsJSON.get("hash") as? String) } catch (e: Exception) { Log.d("Loki", "Failed to unwrap data for message: ${rawMessage.prettifiedDescription()}.") null @@ -524,5 +568,5 @@ object SnodeAPI { // Type Aliases typealias RawResponse = Map<*, *> -typealias MessageListPromise = Promise, Exception> +typealias MessageListPromise = Promise>, Exception> typealias RawResponsePromise = Promise diff --git a/libsignal/protobuf/SignalService.proto b/libsignal/protobuf/SignalService.proto index 7ee8dc9946..390a67a1a8 100644 --- a/libsignal/protobuf/SignalService.proto +++ b/libsignal/protobuf/SignalService.proto @@ -35,12 +35,20 @@ message TypingMessage { required Action action = 2; } +message UnsendRequest { + // @required + required uint64 timestamp = 1; + // @required + required string author = 2; +} + message Content { optional DataMessage dataMessage = 1; optional ReceiptMessage receiptMessage = 5; optional TypingMessage typingMessage = 6; optional ConfigurationMessage configurationMessage = 7; optional DataExtractionNotification dataExtractionNotification = 8; + optional UnsendRequest unsendRequest = 9; } message KeyPair { diff --git a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java index 0ca46d3a9f..4dce0ba511 100644 --- a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java +++ b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java @@ -1706,6 +1706,654 @@ public final class SignalServiceProtos { // @@protoc_insertion_point(class_scope:signalservice.TypingMessage) } + public interface UnsendRequestOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // required uint64 timestamp = 1; + /** + * required uint64 timestamp = 1; + * + *
+     * @required
+     * 
+ */ + boolean hasTimestamp(); + /** + * required uint64 timestamp = 1; + * + *
+     * @required
+     * 
+ */ + long getTimestamp(); + + // required string author = 2; + /** + * required string author = 2; + * + *
+     * @required
+     * 
+ */ + boolean hasAuthor(); + /** + * required string author = 2; + * + *
+     * @required
+     * 
+ */ + java.lang.String getAuthor(); + /** + * required string author = 2; + * + *
+     * @required
+     * 
+ */ + com.google.protobuf.ByteString + getAuthorBytes(); + } + /** + * Protobuf type {@code signalservice.UnsendRequest} + */ + public static final class UnsendRequest extends + com.google.protobuf.GeneratedMessage + implements UnsendRequestOrBuilder { + // Use UnsendRequest.newBuilder() to construct. + private UnsendRequest(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private UnsendRequest(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final UnsendRequest defaultInstance; + public static UnsendRequest getDefaultInstance() { + return defaultInstance; + } + + public UnsendRequest getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private UnsendRequest( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + timestamp_ = input.readUInt64(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + author_ = input.readBytes(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_UnsendRequest_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_UnsendRequest_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.class, org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public UnsendRequest parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new UnsendRequest(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // required uint64 timestamp = 1; + public static final int TIMESTAMP_FIELD_NUMBER = 1; + private long timestamp_; + /** + * required uint64 timestamp = 1; + * + *
+     * @required
+     * 
+ */ + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required uint64 timestamp = 1; + * + *
+     * @required
+     * 
+ */ + public long getTimestamp() { + return timestamp_; + } + + // required string author = 2; + public static final int AUTHOR_FIELD_NUMBER = 2; + private java.lang.Object author_; + /** + * required string author = 2; + * + *
+     * @required
+     * 
+ */ + public boolean hasAuthor() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required string author = 2; + * + *
+     * @required
+     * 
+ */ + public java.lang.String getAuthor() { + java.lang.Object ref = author_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + author_ = s; + } + return s; + } + } + /** + * required string author = 2; + * + *
+     * @required
+     * 
+ */ + public com.google.protobuf.ByteString + getAuthorBytes() { + java.lang.Object ref = author_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + author_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private void initFields() { + timestamp_ = 0L; + author_ = ""; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (!hasTimestamp()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasAuthor()) { + memoizedIsInitialized = 0; + return false; + } + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeUInt64(1, timestamp_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, getAuthorBytes()); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(1, timestamp_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, getAuthorBytes()); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.UnsendRequest prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code signalservice.UnsendRequest} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_UnsendRequest_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_UnsendRequest_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.class, org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder.class); + } + + // Construct using org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + timestamp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000001); + author_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_UnsendRequest_descriptor; + } + + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequest getDefaultInstanceForType() { + return org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance(); + } + + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequest build() { + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequest buildPartial() { + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest result = new org.session.libsignal.protos.SignalServiceProtos.UnsendRequest(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.timestamp_ = timestamp_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.author_ = author_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.session.libsignal.protos.SignalServiceProtos.UnsendRequest) { + return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.UnsendRequest)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.UnsendRequest other) { + if (other == org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance()) return this; + if (other.hasTimestamp()) { + setTimestamp(other.getTimestamp()); + } + if (other.hasAuthor()) { + bitField0_ |= 0x00000002; + author_ = other.author_; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (!hasTimestamp()) { + + return false; + } + if (!hasAuthor()) { + + return false; + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.UnsendRequest) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // required uint64 timestamp = 1; + private long timestamp_ ; + /** + * required uint64 timestamp = 1; + * + *
+       * @required
+       * 
+ */ + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required uint64 timestamp = 1; + * + *
+       * @required
+       * 
+ */ + public long getTimestamp() { + return timestamp_; + } + /** + * required uint64 timestamp = 1; + * + *
+       * @required
+       * 
+ */ + public Builder setTimestamp(long value) { + bitField0_ |= 0x00000001; + timestamp_ = value; + onChanged(); + return this; + } + /** + * required uint64 timestamp = 1; + * + *
+       * @required
+       * 
+ */ + public Builder clearTimestamp() { + bitField0_ = (bitField0_ & ~0x00000001); + timestamp_ = 0L; + onChanged(); + return this; + } + + // required string author = 2; + private java.lang.Object author_ = ""; + /** + * required string author = 2; + * + *
+       * @required
+       * 
+ */ + public boolean hasAuthor() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required string author = 2; + * + *
+       * @required
+       * 
+ */ + public java.lang.String getAuthor() { + java.lang.Object ref = author_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + author_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * required string author = 2; + * + *
+       * @required
+       * 
+ */ + public com.google.protobuf.ByteString + getAuthorBytes() { + java.lang.Object ref = author_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + author_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * required string author = 2; + * + *
+       * @required
+       * 
+ */ + public Builder setAuthor( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + author_ = value; + onChanged(); + return this; + } + /** + * required string author = 2; + * + *
+       * @required
+       * 
+ */ + public Builder clearAuthor() { + bitField0_ = (bitField0_ & ~0x00000002); + author_ = getDefaultInstance().getAuthor(); + onChanged(); + return this; + } + /** + * required string author = 2; + * + *
+       * @required
+       * 
+ */ + public Builder setAuthorBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + author_ = value; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:signalservice.UnsendRequest) + } + + static { + defaultInstance = new UnsendRequest(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:signalservice.UnsendRequest) + } + public interface ContentOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -1778,6 +2426,20 @@ public final class SignalServiceProtos { * optional .signalservice.DataExtractionNotification dataExtractionNotification = 8; */ org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotificationOrBuilder getDataExtractionNotificationOrBuilder(); + + // optional .signalservice.UnsendRequest unsendRequest = 9; + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + boolean hasUnsendRequest(); + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest getUnsendRequest(); + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder getUnsendRequestOrBuilder(); } /** * Protobuf type {@code signalservice.Content} @@ -1895,6 +2557,19 @@ public final class SignalServiceProtos { bitField0_ |= 0x00000010; break; } + case 74: { + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder subBuilder = null; + if (((bitField0_ & 0x00000020) == 0x00000020)) { + subBuilder = unsendRequest_.toBuilder(); + } + unsendRequest_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(unsendRequest_); + unsendRequest_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000020; + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -2045,12 +2720,35 @@ public final class SignalServiceProtos { return dataExtractionNotification_; } + // optional .signalservice.UnsendRequest unsendRequest = 9; + public static final int UNSENDREQUEST_FIELD_NUMBER = 9; + private org.session.libsignal.protos.SignalServiceProtos.UnsendRequest unsendRequest_; + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public boolean hasUnsendRequest() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequest getUnsendRequest() { + return unsendRequest_; + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder getUnsendRequestOrBuilder() { + return unsendRequest_; + } + private void initFields() { dataMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.getDefaultInstance(); receiptMessage_ = org.session.libsignal.protos.SignalServiceProtos.ReceiptMessage.getDefaultInstance(); typingMessage_ = org.session.libsignal.protos.SignalServiceProtos.TypingMessage.getDefaultInstance(); configurationMessage_ = org.session.libsignal.protos.SignalServiceProtos.ConfigurationMessage.getDefaultInstance(); dataExtractionNotification_ = org.session.libsignal.protos.SignalServiceProtos.DataExtractionNotification.getDefaultInstance(); + unsendRequest_ = org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance(); } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -2087,6 +2785,12 @@ public final class SignalServiceProtos { return false; } } + if (hasUnsendRequest()) { + if (!getUnsendRequest().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } memoizedIsInitialized = 1; return true; } @@ -2109,6 +2813,9 @@ public final class SignalServiceProtos { if (((bitField0_ & 0x00000010) == 0x00000010)) { output.writeMessage(8, dataExtractionNotification_); } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeMessage(9, unsendRequest_); + } getUnknownFields().writeTo(output); } @@ -2138,6 +2845,10 @@ public final class SignalServiceProtos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(8, dataExtractionNotification_); } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(9, unsendRequest_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -2251,6 +2962,7 @@ public final class SignalServiceProtos { getTypingMessageFieldBuilder(); getConfigurationMessageFieldBuilder(); getDataExtractionNotificationFieldBuilder(); + getUnsendRequestFieldBuilder(); } } private static Builder create() { @@ -2289,6 +3001,12 @@ public final class SignalServiceProtos { dataExtractionNotificationBuilder_.clear(); } bitField0_ = (bitField0_ & ~0x00000010); + if (unsendRequestBuilder_ == null) { + unsendRequest_ = org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance(); + } else { + unsendRequestBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000020); return this; } @@ -2357,6 +3075,14 @@ public final class SignalServiceProtos { } else { result.dataExtractionNotification_ = dataExtractionNotificationBuilder_.build(); } + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000020; + } + if (unsendRequestBuilder_ == null) { + result.unsendRequest_ = unsendRequest_; + } else { + result.unsendRequest_ = unsendRequestBuilder_.build(); + } result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -2388,6 +3114,9 @@ public final class SignalServiceProtos { if (other.hasDataExtractionNotification()) { mergeDataExtractionNotification(other.getDataExtractionNotification()); } + if (other.hasUnsendRequest()) { + mergeUnsendRequest(other.getUnsendRequest()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -2423,6 +3152,12 @@ public final class SignalServiceProtos { return false; } } + if (hasUnsendRequest()) { + if (!getUnsendRequest().isInitialized()) { + + return false; + } + } return true; } @@ -3030,6 +3765,123 @@ public final class SignalServiceProtos { return dataExtractionNotificationBuilder_; } + // optional .signalservice.UnsendRequest unsendRequest = 9; + private org.session.libsignal.protos.SignalServiceProtos.UnsendRequest unsendRequest_ = org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest, org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder, org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder> unsendRequestBuilder_; + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public boolean hasUnsendRequest() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequest getUnsendRequest() { + if (unsendRequestBuilder_ == null) { + return unsendRequest_; + } else { + return unsendRequestBuilder_.getMessage(); + } + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public Builder setUnsendRequest(org.session.libsignal.protos.SignalServiceProtos.UnsendRequest value) { + if (unsendRequestBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + unsendRequest_ = value; + onChanged(); + } else { + unsendRequestBuilder_.setMessage(value); + } + bitField0_ |= 0x00000020; + return this; + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public Builder setUnsendRequest( + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder builderForValue) { + if (unsendRequestBuilder_ == null) { + unsendRequest_ = builderForValue.build(); + onChanged(); + } else { + unsendRequestBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000020; + return this; + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public Builder mergeUnsendRequest(org.session.libsignal.protos.SignalServiceProtos.UnsendRequest value) { + if (unsendRequestBuilder_ == null) { + if (((bitField0_ & 0x00000020) == 0x00000020) && + unsendRequest_ != org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance()) { + unsendRequest_ = + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.newBuilder(unsendRequest_).mergeFrom(value).buildPartial(); + } else { + unsendRequest_ = value; + } + onChanged(); + } else { + unsendRequestBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000020; + return this; + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public Builder clearUnsendRequest() { + if (unsendRequestBuilder_ == null) { + unsendRequest_ = org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.getDefaultInstance(); + onChanged(); + } else { + unsendRequestBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000020); + return this; + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder getUnsendRequestBuilder() { + bitField0_ |= 0x00000020; + onChanged(); + return getUnsendRequestFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + public org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder getUnsendRequestOrBuilder() { + if (unsendRequestBuilder_ != null) { + return unsendRequestBuilder_.getMessageOrBuilder(); + } else { + return unsendRequest_; + } + } + /** + * optional .signalservice.UnsendRequest unsendRequest = 9; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest, org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder, org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder> + getUnsendRequestFieldBuilder() { + if (unsendRequestBuilder_ == null) { + unsendRequestBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.UnsendRequest, org.session.libsignal.protos.SignalServiceProtos.UnsendRequest.Builder, org.session.libsignal.protos.SignalServiceProtos.UnsendRequestOrBuilder>( + unsendRequest_, + getParentForChildren(), + isClean()); + unsendRequest_ = null; + } + return unsendRequestBuilder_; + } + // @@protoc_insertion_point(builder_scope:signalservice.Content) } @@ -21254,6 +22106,11 @@ public final class SignalServiceProtos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_TypingMessage_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_signalservice_UnsendRequest_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_signalservice_UnsendRequest_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_Content_descriptor; private static @@ -21357,88 +22214,90 @@ public final class SignalServiceProtos { "SSAGE\020\007\"{\n\rTypingMessage\022\021\n\ttimestamp\030\001 " + "\002(\004\0223\n\006action\030\002 \002(\0162#.signalservice.Typi" + "ngMessage.Action\"\"\n\006Action\022\013\n\007STARTED\020\000\022" + - "\013\n\007STOPPED\020\001\"\270\002\n\007Content\022/\n\013dataMessage\030", - "\001 \001(\0132\032.signalservice.DataMessage\0225\n\016rec" + - "eiptMessage\030\005 \001(\0132\035.signalservice.Receip" + - "tMessage\0223\n\rtypingMessage\030\006 \001(\0132\034.signal" + - "service.TypingMessage\022A\n\024configurationMe" + - "ssage\030\007 \001(\0132#.signalservice.Configuratio" + - "nMessage\022M\n\032dataExtractionNotification\030\010" + - " \001(\0132).signalservice.DataExtractionNotif" + - "ication\"0\n\007KeyPair\022\021\n\tpublicKey\030\001 \002(\014\022\022\n" + - "\nprivateKey\030\002 \002(\014\"\226\001\n\032DataExtractionNoti" + - "fication\022<\n\004type\030\001 \002(\0162..signalservice.D", - "ataExtractionNotification.Type\022\021\n\ttimest" + - "amp\030\002 \001(\004\"\'\n\004Type\022\016\n\nSCREENSHOT\020\001\022\017\n\013MED" + - "IA_SAVED\020\002\"\245\014\n\013DataMessage\022\014\n\004body\030\001 \001(\t" + - "\0225\n\013attachments\030\002 \003(\0132 .signalservice.At" + - "tachmentPointer\022*\n\005group\030\003 \001(\0132\033.signals" + - "ervice.GroupContext\022\r\n\005flags\030\004 \001(\r\022\023\n\013ex" + - "pireTimer\030\005 \001(\r\022\022\n\nprofileKey\030\006 \001(\014\022\021\n\tt" + - "imestamp\030\007 \001(\004\022/\n\005quote\030\010 \001(\0132 .signalse" + - "rvice.DataMessage.Quote\0223\n\007preview\030\n \003(\013" + - "2\".signalservice.DataMessage.Preview\0227\n\007", - "profile\030e \001(\0132&.signalservice.DataMessag" + - "e.LokiProfile\022K\n\023openGroupInvitation\030f \001" + - "(\0132..signalservice.DataMessage.OpenGroup" + - "Invitation\022W\n\031closedGroupControlMessage\030" + - "h \001(\01324.signalservice.DataMessage.Closed" + - "GroupControlMessage\022\022\n\nsyncTarget\030i \001(\t\032" + - "\225\002\n\005Quote\022\n\n\002id\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\022\014\n" + - "\004text\030\003 \001(\t\022F\n\013attachments\030\004 \003(\01321.signa" + - "lservice.DataMessage.Quote.QuotedAttachm" + - "ent\032\231\001\n\020QuotedAttachment\022\023\n\013contentType\030", - "\001 \001(\t\022\020\n\010fileName\030\002 \001(\t\0223\n\tthumbnail\030\003 \001" + - "(\0132 .signalservice.AttachmentPointer\022\r\n\005" + - "flags\030\004 \001(\r\"\032\n\005Flags\022\021\n\rVOICE_MESSAGE\020\001\032" + - "V\n\007Preview\022\013\n\003url\030\001 \002(\t\022\r\n\005title\030\002 \001(\t\022/" + - "\n\005image\030\003 \001(\0132 .signalservice.Attachment" + - "Pointer\032:\n\013LokiProfile\022\023\n\013displayName\030\001 " + - "\001(\t\022\026\n\016profilePicture\030\002 \001(\t\0320\n\023OpenGroup" + - "Invitation\022\013\n\003url\030\001 \002(\t\022\014\n\004name\030\003 \002(\t\032\374\003" + - "\n\031ClosedGroupControlMessage\022G\n\004type\030\001 \002(" + - "\01629.signalservice.DataMessage.ClosedGrou", - "pControlMessage.Type\022\021\n\tpublicKey\030\002 \001(\014\022" + - "\014\n\004name\030\003 \001(\t\0221\n\021encryptionKeyPair\030\004 \001(\013" + - "2\026.signalservice.KeyPair\022\017\n\007members\030\005 \003(" + - "\014\022\016\n\006admins\030\006 \003(\014\022U\n\010wrappers\030\007 \003(\0132C.si" + - "gnalservice.DataMessage.ClosedGroupContr" + - "olMessage.KeyPairWrapper\022\027\n\017expirationTi" + - "mer\030\010 \001(\r\032=\n\016KeyPairWrapper\022\021\n\tpublicKey" + - "\030\001 \002(\014\022\030\n\020encryptedKeyPair\030\002 \002(\014\"r\n\004Type" + - "\022\007\n\003NEW\020\001\022\027\n\023ENCRYPTION_KEY_PAIR\020\003\022\017\n\013NA" + - "ME_CHANGE\020\004\022\021\n\rMEMBERS_ADDED\020\005\022\023\n\017MEMBER", - "S_REMOVED\020\006\022\017\n\013MEMBER_LEFT\020\007\"$\n\005Flags\022\033\n" + - "\027EXPIRATION_TIMER_UPDATE\020\002\"\347\003\n\024Configura" + - "tionMessage\022E\n\014closedGroups\030\001 \003(\0132/.sign" + - "alservice.ConfigurationMessage.ClosedGro" + - "up\022\022\n\nopenGroups\030\002 \003(\t\022\023\n\013displayName\030\003 " + - "\001(\t\022\026\n\016profilePicture\030\004 \001(\t\022\022\n\nprofileKe" + - "y\030\005 \001(\014\022=\n\010contacts\030\006 \003(\0132+.signalservic" + - "e.ConfigurationMessage.Contact\032\233\001\n\013Close" + - "dGroup\022\021\n\tpublicKey\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\022" + - "1\n\021encryptionKeyPair\030\003 \001(\0132\026.signalservi", - "ce.KeyPair\022\017\n\007members\030\004 \003(\014\022\016\n\006admins\030\005 " + - "\003(\014\022\027\n\017expirationTimer\030\006 \001(\r\032V\n\007Contact\022" + - "\021\n\tpublicKey\030\001 \002(\014\022\014\n\004name\030\002 \002(\t\022\026\n\016prof" + - "ilePicture\030\003 \001(\t\022\022\n\nprofileKey\030\004 \001(\014\"u\n\016" + - "ReceiptMessage\0220\n\004type\030\001 \002(\0162\".signalser" + - "vice.ReceiptMessage.Type\022\021\n\ttimestamp\030\002 " + - "\003(\004\"\036\n\004Type\022\014\n\010DELIVERY\020\000\022\010\n\004READ\020\001\"\354\001\n\021" + - "AttachmentPointer\022\n\n\002id\030\001 \002(\006\022\023\n\013content" + - "Type\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004size\030\004 \001(\r\022\021\n" + - "\tthumbnail\030\005 \001(\014\022\016\n\006digest\030\006 \001(\014\022\020\n\010file", - "Name\030\007 \001(\t\022\r\n\005flags\030\010 \001(\r\022\r\n\005width\030\t \001(\r" + - "\022\016\n\006height\030\n \001(\r\022\017\n\007caption\030\013 \001(\t\022\013\n\003url" + - "\030e \001(\t\"\032\n\005Flags\022\021\n\rVOICE_MESSAGE\020\001\"\365\001\n\014G" + - "roupContext\022\n\n\002id\030\001 \001(\014\022.\n\004type\030\002 \001(\0162 ." + - "signalservice.GroupContext.Type\022\014\n\004name\030" + - "\003 \001(\t\022\017\n\007members\030\004 \003(\t\0220\n\006avatar\030\005 \001(\0132 " + - ".signalservice.AttachmentPointer\022\016\n\006admi" + - "ns\030\006 \003(\t\"H\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020" + - "\001\022\013\n\007DELIVER\020\002\022\010\n\004QUIT\020\003\022\020\n\014REQUEST_INFO" + - "\020\004B3\n\034org.session.libsignal.protosB\023Sign", - "alServiceProtos" + "\013\n\007STOPPED\020\001\"2\n\rUnsendRequest\022\021\n\ttimesta", + "mp\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\"\355\002\n\007Content\022/\n\013" + + "dataMessage\030\001 \001(\0132\032.signalservice.DataMe" + + "ssage\0225\n\016receiptMessage\030\005 \001(\0132\035.signalse" + + "rvice.ReceiptMessage\0223\n\rtypingMessage\030\006 " + + "\001(\0132\034.signalservice.TypingMessage\022A\n\024con" + + "figurationMessage\030\007 \001(\0132#.signalservice." + + "ConfigurationMessage\022M\n\032dataExtractionNo" + + "tification\030\010 \001(\0132).signalservice.DataExt" + + "ractionNotification\0223\n\runsendRequest\030\t \001" + + "(\0132\034.signalservice.UnsendRequest\"0\n\007KeyP", + "air\022\021\n\tpublicKey\030\001 \002(\014\022\022\n\nprivateKey\030\002 \002" + + "(\014\"\226\001\n\032DataExtractionNotification\022<\n\004typ" + + "e\030\001 \002(\0162..signalservice.DataExtractionNo" + + "tification.Type\022\021\n\ttimestamp\030\002 \001(\004\"\'\n\004Ty" + + "pe\022\016\n\nSCREENSHOT\020\001\022\017\n\013MEDIA_SAVED\020\002\"\245\014\n\013" + + "DataMessage\022\014\n\004body\030\001 \001(\t\0225\n\013attachments" + + "\030\002 \003(\0132 .signalservice.AttachmentPointer" + + "\022*\n\005group\030\003 \001(\0132\033.signalservice.GroupCon" + + "text\022\r\n\005flags\030\004 \001(\r\022\023\n\013expireTimer\030\005 \001(\r" + + "\022\022\n\nprofileKey\030\006 \001(\014\022\021\n\ttimestamp\030\007 \001(\004\022", + "/\n\005quote\030\010 \001(\0132 .signalservice.DataMessa" + + "ge.Quote\0223\n\007preview\030\n \003(\0132\".signalservic" + + "e.DataMessage.Preview\0227\n\007profile\030e \001(\0132&" + + ".signalservice.DataMessage.LokiProfile\022K" + + "\n\023openGroupInvitation\030f \001(\0132..signalserv" + + "ice.DataMessage.OpenGroupInvitation\022W\n\031c" + + "losedGroupControlMessage\030h \001(\01324.signals" + + "ervice.DataMessage.ClosedGroupControlMes" + + "sage\022\022\n\nsyncTarget\030i \001(\t\032\225\002\n\005Quote\022\n\n\002id" + + "\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\022\014\n\004text\030\003 \001(\t\022F\n\013", + "attachments\030\004 \003(\01321.signalservice.DataMe" + + "ssage.Quote.QuotedAttachment\032\231\001\n\020QuotedA" + + "ttachment\022\023\n\013contentType\030\001 \001(\t\022\020\n\010fileNa" + + "me\030\002 \001(\t\0223\n\tthumbnail\030\003 \001(\0132 .signalserv" + + "ice.AttachmentPointer\022\r\n\005flags\030\004 \001(\r\"\032\n\005" + + "Flags\022\021\n\rVOICE_MESSAGE\020\001\032V\n\007Preview\022\013\n\003u" + + "rl\030\001 \002(\t\022\r\n\005title\030\002 \001(\t\022/\n\005image\030\003 \001(\0132 " + + ".signalservice.AttachmentPointer\032:\n\013Loki" + + "Profile\022\023\n\013displayName\030\001 \001(\t\022\026\n\016profileP" + + "icture\030\002 \001(\t\0320\n\023OpenGroupInvitation\022\013\n\003u", + "rl\030\001 \002(\t\022\014\n\004name\030\003 \002(\t\032\374\003\n\031ClosedGroupCo" + + "ntrolMessage\022G\n\004type\030\001 \002(\01629.signalservi" + + "ce.DataMessage.ClosedGroupControlMessage" + + ".Type\022\021\n\tpublicKey\030\002 \001(\014\022\014\n\004name\030\003 \001(\t\0221" + + "\n\021encryptionKeyPair\030\004 \001(\0132\026.signalservic" + + "e.KeyPair\022\017\n\007members\030\005 \003(\014\022\016\n\006admins\030\006 \003" + + "(\014\022U\n\010wrappers\030\007 \003(\0132C.signalservice.Dat" + + "aMessage.ClosedGroupControlMessage.KeyPa" + + "irWrapper\022\027\n\017expirationTimer\030\010 \001(\r\032=\n\016Ke" + + "yPairWrapper\022\021\n\tpublicKey\030\001 \002(\014\022\030\n\020encry", + "ptedKeyPair\030\002 \002(\014\"r\n\004Type\022\007\n\003NEW\020\001\022\027\n\023EN" + + "CRYPTION_KEY_PAIR\020\003\022\017\n\013NAME_CHANGE\020\004\022\021\n\r" + + "MEMBERS_ADDED\020\005\022\023\n\017MEMBERS_REMOVED\020\006\022\017\n\013" + + "MEMBER_LEFT\020\007\"$\n\005Flags\022\033\n\027EXPIRATION_TIM" + + "ER_UPDATE\020\002\"\347\003\n\024ConfigurationMessage\022E\n\014" + + "closedGroups\030\001 \003(\0132/.signalservice.Confi" + + "gurationMessage.ClosedGroup\022\022\n\nopenGroup" + + "s\030\002 \003(\t\022\023\n\013displayName\030\003 \001(\t\022\026\n\016profileP" + + "icture\030\004 \001(\t\022\022\n\nprofileKey\030\005 \001(\014\022=\n\010cont" + + "acts\030\006 \003(\0132+.signalservice.Configuration", + "Message.Contact\032\233\001\n\013ClosedGroup\022\021\n\tpubli" + + "cKey\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\0221\n\021encryptionKe" + + "yPair\030\003 \001(\0132\026.signalservice.KeyPair\022\017\n\007m" + + "embers\030\004 \003(\014\022\016\n\006admins\030\005 \003(\014\022\027\n\017expirati" + + "onTimer\030\006 \001(\r\032V\n\007Contact\022\021\n\tpublicKey\030\001 " + + "\002(\014\022\014\n\004name\030\002 \002(\t\022\026\n\016profilePicture\030\003 \001(" + + "\t\022\022\n\nprofileKey\030\004 \001(\014\"u\n\016ReceiptMessage\022" + + "0\n\004type\030\001 \002(\0162\".signalservice.ReceiptMes" + + "sage.Type\022\021\n\ttimestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010" + + "DELIVERY\020\000\022\010\n\004READ\020\001\"\354\001\n\021AttachmentPoint", + "er\022\n\n\002id\030\001 \002(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003k" + + "ey\030\003 \001(\014\022\014\n\004size\030\004 \001(\r\022\021\n\tthumbnail\030\005 \001(" + + "\014\022\016\n\006digest\030\006 \001(\014\022\020\n\010fileName\030\007 \001(\t\022\r\n\005f" + + "lags\030\010 \001(\r\022\r\n\005width\030\t \001(\r\022\016\n\006height\030\n \001(" + + "\r\022\017\n\007caption\030\013 \001(\t\022\013\n\003url\030e \001(\t\"\032\n\005Flags" + + "\022\021\n\rVOICE_MESSAGE\020\001\"\365\001\n\014GroupContext\022\n\n\002" + + "id\030\001 \001(\014\022.\n\004type\030\002 \001(\0162 .signalservice.G" + + "roupContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007member" + + "s\030\004 \003(\t\0220\n\006avatar\030\005 \001(\0132 .signalservice." + + "AttachmentPointer\022\016\n\006admins\030\006 \003(\t\"H\n\004Typ", + "e\022\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n\007DELIVER\020\002\022" + + "\010\n\004QUIT\020\003\022\020\n\014REQUEST_INFO\020\004B3\n\034org.sessi" + + "on.libsignal.protosB\023SignalServiceProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -21457,26 +22316,32 @@ public final class SignalServiceProtos { com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_TypingMessage_descriptor, new java.lang.String[] { "Timestamp", "Action", }); - internal_static_signalservice_Content_descriptor = + internal_static_signalservice_UnsendRequest_descriptor = getDescriptor().getMessageTypes().get(2); + internal_static_signalservice_UnsendRequest_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_signalservice_UnsendRequest_descriptor, + new java.lang.String[] { "Timestamp", "Author", }); + internal_static_signalservice_Content_descriptor = + getDescriptor().getMessageTypes().get(3); internal_static_signalservice_Content_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_Content_descriptor, - new java.lang.String[] { "DataMessage", "ReceiptMessage", "TypingMessage", "ConfigurationMessage", "DataExtractionNotification", }); + new java.lang.String[] { "DataMessage", "ReceiptMessage", "TypingMessage", "ConfigurationMessage", "DataExtractionNotification", "UnsendRequest", }); internal_static_signalservice_KeyPair_descriptor = - getDescriptor().getMessageTypes().get(3); + getDescriptor().getMessageTypes().get(4); internal_static_signalservice_KeyPair_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_KeyPair_descriptor, new java.lang.String[] { "PublicKey", "PrivateKey", }); internal_static_signalservice_DataExtractionNotification_descriptor = - getDescriptor().getMessageTypes().get(4); + getDescriptor().getMessageTypes().get(5); internal_static_signalservice_DataExtractionNotification_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataExtractionNotification_descriptor, new java.lang.String[] { "Type", "Timestamp", }); internal_static_signalservice_DataMessage_descriptor = - getDescriptor().getMessageTypes().get(5); + getDescriptor().getMessageTypes().get(6); internal_static_signalservice_DataMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_descriptor, @@ -21524,7 +22389,7 @@ public final class SignalServiceProtos { internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_descriptor, new java.lang.String[] { "PublicKey", "EncryptedKeyPair", }); internal_static_signalservice_ConfigurationMessage_descriptor = - getDescriptor().getMessageTypes().get(6); + getDescriptor().getMessageTypes().get(7); internal_static_signalservice_ConfigurationMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_ConfigurationMessage_descriptor, @@ -21542,19 +22407,19 @@ public final class SignalServiceProtos { internal_static_signalservice_ConfigurationMessage_Contact_descriptor, new java.lang.String[] { "PublicKey", "Name", "ProfilePicture", "ProfileKey", }); internal_static_signalservice_ReceiptMessage_descriptor = - getDescriptor().getMessageTypes().get(7); + getDescriptor().getMessageTypes().get(8); internal_static_signalservice_ReceiptMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_ReceiptMessage_descriptor, new java.lang.String[] { "Type", "Timestamp", }); internal_static_signalservice_AttachmentPointer_descriptor = - getDescriptor().getMessageTypes().get(8); + getDescriptor().getMessageTypes().get(9); internal_static_signalservice_AttachmentPointer_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_AttachmentPointer_descriptor, new java.lang.String[] { "Id", "ContentType", "Key", "Size", "Thumbnail", "Digest", "FileName", "Flags", "Width", "Height", "Caption", "Url", }); internal_static_signalservice_GroupContext_descriptor = - getDescriptor().getMessageTypes().get(9); + getDescriptor().getMessageTypes().get(10); internal_static_signalservice_GroupContext_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_GroupContext_descriptor, diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt b/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt index 7b1592a527..cfbedb7338 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt @@ -7,6 +7,7 @@ class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) { GetSwarm("get_snodes_for_pubkey"), GetMessages("retrieve"), SendMessage("store"), + DeleteMessage("delete"), OxenDaemonRPCCall("oxend_request"), Info("info"), DeleteAll("delete_all")