From 2466d9b4c03f1b9f9422e87d23838b7bb87a7bf1 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Mon, 28 Aug 2023 09:51:48 +1000 Subject: [PATCH] [SES-1002] Synced blind requests (#1303) * feat: update config to use blinded-msg-requests pr * feat: add block community message requests bool to protos * feat: add everything needed for recipientDB to have blocked community requests potentially * feat: add db migrations * feat: add sending community block flags and preference options * feat: add parsing block request flag * fix: open group message requests were broken anyway * fix: delete all encoded open group inbox ID bs, fix privacy settings using user config as privacy store * feat: initial creation sets flag, rename to match libsession implementation value * fix: recipient blinded checks from open group message for blocking community requests on blinded ID version of recipient, use correct (inverted) values from before for checking polling and empty states etc * fix: pr comments for view model factory context ref, simplified user config object check for category in PrivacySettingsPreferenceFragment * fix: pr comments * fix: migrate some dependencies and functionality out of VM into repository to remove content resolver and context dependecy so tests pass again * refactor: better naming for hidesInputBar and add more tests for expected recipient view states * fix: use contact information as opposed to active conversations * fix: PR comments --- .../components/ProfilePictureView.kt | 3 +- .../conversation/v2/ConversationActivityV2.kt | 42 +++-- .../conversation/v2/ConversationViewModel.kt | 40 +++-- .../securesms/database/RecipientDatabase.java | 19 ++- .../securesms/database/Storage.kt | 27 ++-- .../database/helpers/SQLCipherOpenHelper.java | 8 +- .../securesms/dependencies/ContentModule.kt | 17 ++ .../securesms/home/HomeActivity.kt | 19 ++- .../preferences/PrivacySettingsActivity.kt | 2 + .../PrivacySettingsPreferenceFragment.kt | 36 +++++ .../repository/ConversationRepository.kt | 28 +++- app/src/main/res/values/strings.xml | 4 + .../res/xml/preferences_app_protection.xml | 6 + .../v2/ConversationViewModelTest.kt | 50 ++++-- libsession-util/libsession-util | 2 +- libsession-util/src/main/cpp/user_profile.cpp | 29 ++++ .../loki/messenger/libsession_util/Config.kt | 3 + .../libsession/database/StorageProtocol.kt | 2 + .../messages/visible/VisibleMessage.kt | 8 +- .../messaging/open_groups/OpenGroupApi.kt | 3 +- .../sending_receiving/MessageSender.kt | 7 + .../ReceivedMessageHandler.kt | 4 + .../session/libsession/utilities/GroupUtil.kt | 16 +- .../utilities/TextSecurePreferences.kt | 2 + .../utilities/recipients/Recipient.java | 153 ++++++++++++------ .../recipients/RecipientProvider.java | 2 + libsignal/protobuf/SignalService.proto | 29 ++-- .../libsignal/protos/SignalServiceProtos.java | 118 +++++++++++--- 28 files changed, 522 insertions(+), 157 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/dependencies/ContentModule.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index 604422460..7635ad40e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -10,7 +10,6 @@ import androidx.annotation.DimenRes import com.bumptech.glide.load.engine.DiskCacheStrategy import network.loki.messenger.R import network.loki.messenger.databinding.ViewProfilePictureBinding -import network.loki.messenger.databinding.ViewUserBinding import org.session.libsession.avatars.ContactColors import org.session.libsession.avatars.PlaceholderAvatarPhoto import org.session.libsession.avatars.ProfileContactPhoto @@ -74,7 +73,7 @@ class ProfilePictureView @JvmOverloads constructor( additionalDisplayName = getUserDisplayName(apk) } } else if(recipient.isOpenGroupInboxRecipient) { - val publicKey = GroupUtil.getDecodedOpenGroupInbox(recipient.address.serialize()) + val publicKey = GroupUtil.getDecodedOpenGroupInboxSessionId(recipient.address.serialize()) this.publicKey = publicKey displayName = getUserDisplayName(publicKey) additionalPublicKey = null 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 64bc0a4e7..e1515109f 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 @@ -40,6 +40,7 @@ import androidx.annotation.DimenRes import androidx.core.text.set import androidx.core.text.toSpannable import androidx.core.view.drawToBitmap +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.lifecycle.Lifecycle @@ -78,7 +79,6 @@ import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.OpenGroupApi -import org.session.libsession.messaging.open_groups.OpenGroupApi.Capability 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 @@ -105,13 +105,12 @@ import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.attachments.ScreenshotObserver import org.thoughtcrime.securesms.audio.AudioRecorder import org.thoughtcrime.securesms.contacts.SelectContactsActivity.Companion.selectedContactsKey -import org.thoughtcrime.securesms.util.SimpleTextWatcher import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnActionSelectedListener import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnReactionSelectedListener import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.MESSAGE_TIMESTAMP +import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_DELETE import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_REPLY import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_RESEND -import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_DELETE import org.thoughtcrime.securesms.conversation.v2.dialogs.BlockedDialog import org.thoughtcrime.securesms.conversation.v2.dialogs.LinkPreviewDialog import org.thoughtcrime.securesms.conversation.v2.dialogs.SendSeedDialog @@ -174,6 +173,7 @@ import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.SaveAttachmentTask +import org.thoughtcrime.securesms.util.SimpleTextWatcher import org.thoughtcrime.securesms.util.isScrolledToBottom import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.toPx @@ -240,11 +240,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val address = if (sessionId.prefix == IdPrefix.BLINDED && openGroup != null) { storage.getOrCreateBlindedIdMapping(sessionId.hexString, openGroup.server, openGroup.publicKey).sessionId?.let { fromSerialized(it) - } ?: run { - val openGroupInboxId = - "${openGroup.server}!${openGroup.publicKey}!${sessionId.hexString}".toByteArray() - fromSerialized(GroupUtil.getEncodedOpenGroupInboxID(openGroupInboxId)) - } + } ?: GroupUtil.getEncodedOpenGroupInboxID(openGroup, sessionId) } else { it } @@ -253,7 +249,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } ?: finish() } - viewModelFactory.create(threadId, MessagingModuleConfiguration.shared.getUserED25519KeyPair(), contentResolver) + viewModelFactory.create(threadId, MessagingModuleConfiguration.shared.getUserED25519KeyPair()) } private var actionMode: ActionMode? = null private var unreadCount = 0 @@ -312,8 +308,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe handleSwipeToReply(message) }, onItemLongPress = { message, position, view -> - if (!isMessageRequestThread() && - (viewModel.openGroup == null || Capability.REACTIONS.name.lowercase() in viewModel.serverCapabilities) + if (!viewModel.isMessageRequestThread && + viewModel.canReactToMessages ) { showEmojiPicker(message, view) } else { @@ -596,26 +592,27 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // called from onCreate private fun setUpInputBar() { - binding!!.inputBar.isVisible = viewModel.openGroup == null || viewModel.openGroup?.canWrite == true - binding!!.inputBar.delegate = this - binding!!.inputBarRecordingView.delegate = this + val binding = binding ?: return + binding.inputBar.isGone = viewModel.hidesInputBar() + binding.inputBar.delegate = this + binding.inputBarRecordingView.delegate = this // GIF button - binding!!.gifButtonContainer.addView(gifButton) + binding.gifButtonContainer.addView(gifButton) gifButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) gifButton.onUp = { showGIFPicker() } gifButton.snIsEnabled = false // Document button - binding!!.documentButtonContainer.addView(documentButton) + binding.documentButtonContainer.addView(documentButton) documentButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) documentButton.onUp = { showDocumentPicker() } documentButton.snIsEnabled = false // Library button - binding!!.libraryButtonContainer.addView(libraryButton) + binding.libraryButtonContainer.addView(libraryButton) libraryButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) libraryButton.onUp = { pickFromLibrary() } libraryButton.snIsEnabled = false // Camera button - binding!!.cameraButtonContainer.addView(cameraButton) + binding.cameraButtonContainer.addView(cameraButton) cameraButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) cameraButton.onUp = { showCamera() } cameraButton.snIsEnabled = false @@ -764,7 +761,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe override fun onPrepareOptionsMenu(menu: Menu): Boolean { val recipient = viewModel.recipient ?: return false - if (!isMessageRequestThread()) { + if (!viewModel.isMessageRequestThread) { ConversationMenuHelper.onPrepareOptionsMenu( menu, menuInflater, @@ -850,11 +847,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } - private fun isMessageRequestThread(): Boolean { - val recipient = viewModel.recipient ?: return false - return !recipient.isGroupRecipient && !recipient.isApproved - } - private fun isOutgoingMessageRequestThread(): Boolean { val recipient = viewModel.recipient ?: return false return !recipient.isGroupRecipient && @@ -1069,11 +1061,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private fun updatePlaceholder() { val recipient = viewModel.recipient ?: return Log.w("Loki", "recipient was null in placeholder update") + val blindedRecipient = viewModel.blindedRecipient val binding = binding ?: return val openGroup = viewModel.openGroup val (textResource, insertParam) = when { recipient.isLocalNumber -> R.string.activity_conversation_empty_state_note_to_self to null openGroup != null && !openGroup.canWrite -> R.string.activity_conversation_empty_state_read_only to recipient.toShortString() + blindedRecipient?.blocksCommunityMessageRequests == true -> R.string.activity_conversation_empty_state_blocks_community_requests to recipient.toShortString() else -> R.string.activity_conversation_empty_state_default to recipient.toShortString() } val showPlaceholder = adapter.itemCount == 0 diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 13736974b..532e65e19 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -1,10 +1,8 @@ package org.thoughtcrime.securesms.conversation.v2 -import android.content.ContentResolver import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -import app.cash.copper.flow.observeQuery import com.goterl.lazysodium.utils.KeyPair import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -21,7 +19,6 @@ import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.repository.ConversationRepository @@ -30,7 +27,6 @@ import java.util.UUID class ConversationViewModel( val threadId: Long, val edKeyPair: KeyPair?, - private val contentResolver: ContentResolver, private val repository: ConversationRepository, private val storage: Storage ) : ViewModel() { @@ -47,6 +43,15 @@ class ConversationViewModel( val recipient: Recipient? get() = _recipient.value + val blindedRecipient: Recipient? + get() = _recipient.value?.let { recipient -> + when { + recipient.isOpenGroupOutboxRecipient -> recipient + recipient.isOpenGroupInboxRecipient -> repository.maybeGetBlindedRecipient(recipient) + else -> null + } + } + private var _openGroup: RetrieveOnce = RetrieveOnce { storage.getOpenGroup(threadId) } @@ -62,12 +67,22 @@ class ConversationViewModel( ?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString } + val isMessageRequestThread : Boolean + get() { + val recipient = recipient ?: return false + return !recipient.isLocalNumber && !recipient.isGroupRecipient && !recipient.isApproved + } + + val canReactToMessages: Boolean + // allow reactions if the open group is null (normal conversations) or the open group's capabilities include reactions + get() = (openGroup == null || OpenGroupApi.Capability.REACTIONS.name.lowercase() in serverCapabilities) + + init { viewModelScope.launch(Dispatchers.IO) { - contentResolver.observeQuery(DatabaseContentProviders.Conversation.getUriForThread(threadId)) - .collect { - val recipientExists = storage.getRecipientForThread(threadId) != null - if (!recipientExists && _uiState.value.conversationExists) { + repository.recipientUpdateFlow(threadId) + .collect { recipient -> + if (recipient == null && _uiState.value.conversationExists) { _uiState.update { it.copy(conversationExists = false) } } } @@ -199,22 +214,25 @@ class ConversationViewModel( _recipient.updateTo(repository.maybeGetRecipientForThreadId(threadId)) } + fun hidesInputBar(): Boolean = openGroup?.canWrite != true && + blindedRecipient?.blocksCommunityMessageRequests == true + + @dagger.assisted.AssistedFactory interface AssistedFactory { - fun create(threadId: Long, edKeyPair: KeyPair?, contentResolver: ContentResolver): Factory + fun create(threadId: Long, edKeyPair: KeyPair?): Factory } @Suppress("UNCHECKED_CAST") class Factory @AssistedInject constructor( @Assisted private val threadId: Long, @Assisted private val edKeyPair: KeyPair?, - @Assisted private val contentResolver: ContentResolver, private val repository: ConversationRepository, private val storage: Storage ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return ConversationViewModel(threadId, edKeyPair, contentResolver, repository, storage) as T + return ConversationViewModel(threadId, edKeyPair, repository, storage) as T } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index b7b836418..dde5847ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -63,13 +63,14 @@ public class RecipientDatabase extends Database { private static final String FORCE_SMS_SELECTION = "force_sms_selection"; private static final String NOTIFY_TYPE = "notify_type"; // all, mentions only, none private static final String WRAPPER_HASH = "wrapper_hash"; + private static final String BLOCKS_COMMUNITY_MESSAGE_REQUESTS = "blocks_community_message_requests"; private static final String[] RECIPIENT_PROJECTION = new String[] { BLOCK, APPROVED, APPROVED_ME, NOTIFICATION, CALL_RINGTONE, VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, EXPIRE_MESSAGES, REGISTERED, PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_CONTACT_URI, SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL, UNIDENTIFIED_ACCESS_MODE, - FORCE_SMS_SELECTION, NOTIFY_TYPE, WRAPPER_HASH + FORCE_SMS_SELECTION, NOTIFY_TYPE, WRAPPER_HASH, BLOCKS_COMMUNITY_MESSAGE_REQUESTS }; static final List TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION) @@ -142,6 +143,11 @@ public class RecipientDatabase extends Database { "ADD COLUMN "+WRAPPER_HASH+" TEXT DEFAULT NULL;"; } + public static String getAddBlocksCommunityMessageRequests() { + return "ALTER TABLE "+TABLE_NAME+" "+ + "ADD COLUMN "+BLOCKS_COMMUNITY_MESSAGE_REQUESTS+" INT DEFAULT 0;"; + } + public static final int NOTIFY_TYPE_ALL = 0; public static final int NOTIFY_TYPE_MENTIONS = 1; public static final int NOTIFY_TYPE_NONE = 2; @@ -197,6 +203,7 @@ public class RecipientDatabase extends Database { int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE)); boolean forceSmsSelection = cursor.getInt(cursor.getColumnIndexOrThrow(FORCE_SMS_SELECTION)) == 1; String wrapperHash = cursor.getString(cursor.getColumnIndexOrThrow(WRAPPER_HASH)); + boolean blocksCommunityMessageRequests = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCKS_COMMUNITY_MESSAGE_REQUESTS)) == 1; MaterialColor color; byte[] profileKey = null; @@ -228,7 +235,7 @@ public class RecipientDatabase extends Database { systemPhoneLabel, systemContactUri, signalProfileName, signalProfileAvatar, profileSharing, notificationChannel, Recipient.UnidentifiedAccessMode.fromMode(unidentifiedAccessMode), - forceSmsSelection, wrapperHash)); + forceSmsSelection, wrapperHash, blocksCommunityMessageRequests)); } public void setColor(@NonNull Recipient recipient, @NonNull MaterialColor color) { @@ -395,6 +402,14 @@ public class RecipientDatabase extends Database { notifyRecipientListeners(); } + public void setBlocksCommunityMessageRequests(@NonNull Recipient recipient, boolean isBlocked) { + ContentValues contentValues = new ContentValues(1); + contentValues.put(BLOCKS_COMMUNITY_MESSAGE_REQUESTS, isBlocked ? 1 : 0); + updateOrInsert(recipient.getAddress(), contentValues); + recipient.resolve().setBlocksCommunityMessageRequests(isBlocked); + notifyRecipientListeners(); + } + private void updateOrInsert(Address address, ContentValues contentValues) { SQLiteDatabase database = 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 9b038b995..481a4fa06 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -190,6 +190,11 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co db.setProfileKey(recipient, newProfileKey) } + override fun setBlocksCommunityMessageRequests(recipient: Recipient, blocksMessageRequests: Boolean) { + val db = DatabaseComponent.get(context).recipientDatabase() + db.setBlocksCommunityMessageRequests(recipient, blocksMessageRequests) + } + override fun setUserProfilePicture(newProfilePicture: String?, newProfileKey: ByteArray?) { val ourRecipient = fromSerialized(getUserPublicKey()!!).let { Recipient.from(context, it, false) @@ -430,6 +435,10 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co return configFactory.canPerformChange(variant, publicKey, changeTimestampMs) } + override fun isCheckingCommunityRequests(): Boolean { + return configFactory.user?.getCommunityMessageRequests() == true + } + fun notifyUpdates(forConfigObject: ConfigBase) { when (forConfigObject) { is UserProfile -> updateUser(forConfigObject) @@ -1405,7 +1414,7 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co val blindedId = when { recipient.isGroupRecipient -> null recipient.isOpenGroupInboxRecipient -> { - GroupUtil.getDecodedOpenGroupInbox(address) + GroupUtil.getDecodedOpenGroupInboxSessionId(address) } else -> { if (SessionId(address).prefix == IdPrefix.BLINDED) { @@ -1524,16 +1533,12 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co if (mapping.sessionId != null) { return mapping } - val threadDb = DatabaseComponent.get(context).threadDatabase() - threadDb.readerFor(threadDb.conversationList).use { reader -> - while (reader.next != null) { - val recipient = reader.current.recipient - val sessionId = recipient.address.serialize() - if (!recipient.isGroupRecipient && SodiumUtilities.sessionId(sessionId, blindedId, serverPublicKey)) { - val contactMapping = mapping.copy(sessionId = sessionId) - db.addBlindedIdMapping(contactMapping) - return contactMapping - } + getAllContacts().forEach { contact -> + val sessionId = SessionId(contact.sessionID) + if (sessionId.prefix == IdPrefix.STANDARD && SodiumUtilities.sessionId(sessionId.hexString, blindedId, serverPublicKey)) { + val contactMapping = mapping.copy(sessionId = sessionId.hexString) + db.addBlindedIdMapping(contactMapping) + return contactMapping } } db.getBlindedIdMappingsExceptFor(server).forEach { 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 89bda0994..1b65706a5 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 @@ -88,9 +88,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV40 = 61; private static final int lokiV41 = 62; private static final int lokiV42 = 63; + private static final int lokiV43 = 64; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV42; + private static final int DATABASE_VERSION = lokiV43; private static final int MIN_DATABASE_VERSION = lokiV7; private static final String CIPHER3_DATABASE_NAME = "signal.db"; public static final String DATABASE_NAME = "signal_v4.db"; @@ -356,6 +357,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { executeStatements(db, ReactionDatabase.CREATE_REACTION_TRIGGERS); db.execSQL(RecipientDatabase.getAddWrapperHash()); + db.execSQL(RecipientDatabase.getAddBlocksCommunityMessageRequests()); } @Override @@ -598,6 +600,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(RecipientDatabase.getAddWrapperHash()); } + if (oldVersion < lokiV43) { + db.execSQL(RecipientDatabase.getAddBlocksCommunityMessageRequests()); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ContentModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ContentModule.kt new file mode 100644 index 000000000..89098a0f1 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ContentModule.kt @@ -0,0 +1,17 @@ +package org.thoughtcrime.securesms.dependencies + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +object ContentModule { + + @Provides + fun providesContentResolver(@ApplicationContext context: Context) =context.contentResolver + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 59f324b52..ba519f0a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -72,8 +72,8 @@ import org.thoughtcrime.securesms.onboarding.SeedActivity import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.preferences.SettingsActivity -import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.showMuteDialog +import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.IP2Country @@ -299,12 +299,17 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), } EventBus.getDefault().register(this@HomeActivity) if (intent.hasExtra(FROM_ONBOARDING) - && intent.getBooleanExtra(FROM_ONBOARDING, false) - && !(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled() - ) { - Permissions.with(this) - .request(Manifest.permission.POST_NOTIFICATIONS) - .execute() + && intent.getBooleanExtra(FROM_ONBOARDING, false)) { + if ((getSystemService(NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled().not()) { + Permissions.with(this) + .request(Manifest.permission.POST_NOTIFICATIONS) + .execute() + } + configFactory.user?.let { user -> + if (!user.isBlockCommunityMessageRequestsSet()) { + user.setCommunityMessageRequests(false) + } + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsActivity.kt index b8606a3d5..de136694f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsActivity.kt @@ -1,9 +1,11 @@ package org.thoughtcrime.securesms.preferences import android.os.Bundle +import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity +@AndroidEntryPoint class PrivacySettingsActivity : PassphraseRequiredActionBarActivity() { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.kt index eaf48f868..a04e71ddf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.kt @@ -8,6 +8,9 @@ import android.os.Build import android.os.Bundle import android.provider.Settings import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceDataStore +import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.BuildConfig import network.loki.messenger.R import org.session.libsession.utilities.TextSecurePreferences @@ -15,13 +18,19 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.isPasswo import org.session.libsession.utilities.TextSecurePreferences.Companion.setScreenLockEnabled import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.components.SwitchPreferenceCompat +import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.service.KeyCachingService import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.areNotificationsEnabled import org.thoughtcrime.securesms.util.IntentUtils +import javax.inject.Inject +@AndroidEntryPoint class PrivacySettingsPreferenceFragment : ListSummaryPreferenceFragment() { + + @Inject lateinit var configFactory: ConfigFactory + override fun onCreate(paramBundle: Bundle?) { super.onCreate(paramBundle) findPreference(TextSecurePreferences.SCREEN_LOCK)!! @@ -30,6 +39,33 @@ class PrivacySettingsPreferenceFragment : ListSummaryPreferenceFragment() { .onPreferenceChangeListener = TypingIndicatorsToggleListener() findPreference(TextSecurePreferences.CALL_NOTIFICATIONS_ENABLED)!! .onPreferenceChangeListener = CallToggleListener(this) { setCall(it) } + findPreference(getString(R.string.preferences__message_requests_category))?.let { category -> + when (val user = configFactory.user) { + null -> category.isVisible = false + else -> SwitchPreferenceCompat(requireContext()).apply { + key = TextSecurePreferences.ALLOW_MESSAGE_REQUESTS + preferenceDataStore = object : PreferenceDataStore() { + + override fun getBoolean(key: String?, defValue: Boolean): Boolean { + if (key == TextSecurePreferences.ALLOW_MESSAGE_REQUESTS) { + return user.getCommunityMessageRequests() + } + return super.getBoolean(key, defValue) + } + + override fun putBoolean(key: String?, value: Boolean) { + if (key == TextSecurePreferences.ALLOW_MESSAGE_REQUESTS) { + user.setCommunityMessageRequests(value) + return + } + super.putBoolean(key, value) + } + } + title = getString(R.string.preferences__message_requests_title) + summary = getString(R.string.preferences__message_requests_summary) + }.let(category::addPreference) + } + } initializeVisibility() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index dd013afa7..2c0b39d69 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -1,5 +1,11 @@ package org.thoughtcrime.securesms.repository +import android.content.ContentResolver +import android.content.Context +import app.cash.copper.flow.observeQuery +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import org.session.libsession.database.MessageDataProvider import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.control.MessageRequestResponse @@ -15,6 +21,7 @@ import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.toHexString +import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.DraftDatabase import org.thoughtcrime.securesms.database.LokiMessageDatabase import org.thoughtcrime.securesms.database.LokiThreadDatabase @@ -35,6 +42,8 @@ import kotlin.coroutines.suspendCoroutine interface ConversationRepository { fun maybeGetRecipientForThreadId(threadId: Long): Recipient? + fun maybeGetBlindedRecipient(recipient: Recipient): Recipient? + fun recipientUpdateFlow(threadId: Long): Flow fun saveDraft(threadId: Long, text: String) fun getDraft(threadId: Long): String? fun clearDrafts(threadId: Long) @@ -75,6 +84,7 @@ interface ConversationRepository { } class DefaultConversationRepository @Inject constructor( + @ApplicationContext private val context: Context, private val textSecurePreferences: TextSecurePreferences, private val messageDataProvider: MessageDataProvider, private val threadDb: ThreadDatabase, @@ -87,13 +97,29 @@ class DefaultConversationRepository @Inject constructor( private val storage: Storage, private val lokiMessageDb: LokiMessageDatabase, private val sessionJobDb: SessionJobDatabase, - private val configFactory: ConfigFactory + private val configFactory: ConfigFactory, + private val contentResolver: ContentResolver, ) : ConversationRepository { override fun maybeGetRecipientForThreadId(threadId: Long): Recipient? { return threadDb.getRecipientForThreadId(threadId) } + override fun maybeGetBlindedRecipient(recipient: Recipient): Recipient? { + if (!recipient.isOpenGroupInboxRecipient) return null + return Recipient.from( + context, + Address.fromSerialized(GroupUtil.getDecodedOpenGroupInboxSessionId(recipient.address.serialize())), + false + ) + } + + override fun recipientUpdateFlow(threadId: Long): Flow { + return contentResolver.observeQuery(DatabaseContentProviders.Conversation.getUriForThread(threadId)).map { + maybeGetRecipientForThreadId(threadId) + } + } + override fun saveDraft(threadId: Long, text: String) { if (text.isEmpty()) return val drafts = DraftDatabase.Drafts() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2d46f6860..64c2421dc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -627,6 +627,9 @@ Priority Screenshot Notifications Receive a notification when a contact takes a screenshot of a one-to-one chat. + Message Requests + Community Message Requests + Allow message requests from Community conversations @@ -1033,6 +1036,7 @@ Some of your devices are using outdated versions. Syncing may be unreliable until they are updated. There are no messages in %s. + %s has message requests from Community conversations turned off, so you cannot send them a message. You have no messages in Note to Self. You have no messages from %s.\nSend a message to start the conversation! diff --git a/app/src/main/res/xml/preferences_app_protection.xml b/app/src/main/res/xml/preferences_app_protection.xml index 12607ee76..6e0cd98c2 100644 --- a/app/src/main/res/xml/preferences_app_protection.xml +++ b/app/src/main/res/xml/preferences_app_protection.xml @@ -20,6 +20,12 @@ + + + () + private val storage = mock() private val threadId = 123L - private val edKeyPair = mock(KeyPair::class.java) + private val edKeyPair = mock() private lateinit var recipient: Recipient private val viewModel: ConversationViewModel by lazy { - ConversationViewModel(threadId, edKeyPair, mock(), repository, storage) + ConversationViewModel(threadId, edKeyPair, repository, storage) } @Before fun setUp() { - recipient = mock(Recipient::class.java) + recipient = mock() whenever(repository.maybeGetRecipientForThreadId(anyLong())).thenReturn(recipient) + whenever(repository.recipientUpdateFlow(anyLong())).thenReturn(emptyFlow()) } @Test @@ -79,7 +83,7 @@ class ConversationViewModelTest: BaseViewModelTest() { @Test fun `should delete locally`() { - val message = mock(MessageRecord::class.java) + val message = mock() viewModel.deleteLocally(message) @@ -88,7 +92,7 @@ class ConversationViewModelTest: BaseViewModelTest() { @Test fun `should emit error message on failure to delete a message for everyone`() = runBlockingTest { - val message = mock(MessageRecord::class.java) + val message = mock() val error = Throwable() whenever(repository.deleteForEveryone(anyLong(), any(), any())) .thenReturn(ResultOf.Failure(error)) @@ -101,7 +105,7 @@ class ConversationViewModelTest: BaseViewModelTest() { @Test fun `should emit error message on failure to delete messages without unsend request`() = runBlockingTest { - val message = mock(MessageRecord::class.java) + val message = mock() val error = Throwable() whenever(repository.deleteMessageWithoutUnsendRequest(anyLong(), anySet())) .thenReturn(ResultOf.Failure(error)) @@ -181,4 +185,30 @@ class ConversationViewModelTest: BaseViewModelTest() { assertThat(viewModel.uiState.value.uiMessages.size, equalTo(0)) } + @Test + fun `open group recipient should have no blinded recipient`() { + whenever(recipient.isOpenGroupRecipient).thenReturn(true) + whenever(recipient.isOpenGroupOutboxRecipient).thenReturn(false) + whenever(recipient.isOpenGroupInboxRecipient).thenReturn(false) + assertThat(viewModel.blindedRecipient, nullValue()) + } + + @Test + fun `local recipient should have input and no blinded recipient`() { + whenever(recipient.isLocalNumber).thenReturn(true) + assertThat(viewModel.hidesInputBar(), equalTo(false)) + assertThat(viewModel.blindedRecipient, nullValue()) + } + + @Test + fun `contact recipient should hide input bar if not accepting requests`() { + whenever(recipient.isOpenGroupInboxRecipient).thenReturn(true) + val blinded = mock { + whenever(it.blocksCommunityMessageRequests).thenReturn(true) + } + whenever(repository.maybeGetBlindedRecipient(recipient)).thenReturn(blinded) + assertThat(viewModel.blindedRecipient, notNullValue()) + assertThat(viewModel.hidesInputBar(), equalTo(true)) + } + } \ No newline at end of file diff --git a/libsession-util/libsession-util b/libsession-util/libsession-util index 7eb870283..e3ccf29db 160000 --- a/libsession-util/libsession-util +++ b/libsession-util/libsession-util @@ -1 +1 @@ -Subproject commit 7eb87028355bfc89950102c52d5b2927a25b2e22 +Subproject commit e3ccf29db08aaf0b9bb6bbe72ae5967cd183a78d diff --git a/libsession-util/src/main/cpp/user_profile.cpp b/libsession-util/src/main/cpp/user_profile.cpp index 78b671ef0..5b3980e63 100644 --- a/libsession-util/src/main/cpp/user_profile.cpp +++ b/libsession-util/src/main/cpp/user_profile.cpp @@ -95,4 +95,33 @@ Java_network_loki_messenger_libsession_1util_UserProfile_getNtsPriority(JNIEnv * std::lock_guard lock{util::util_mutex_}; auto profile = ptrToProfile(env, thiz); return profile->get_nts_priority(); +} +extern "C" +JNIEXPORT jboolean JNICALL +Java_network_loki_messenger_libsession_1util_UserProfile_getCommunityMessageRequests( + JNIEnv *env, jobject thiz) { + std::lock_guard lock{util::util_mutex_}; + auto profile = ptrToProfile(env, thiz); + auto blinded_msg_requests = profile->get_blinded_msgreqs(); + if (blinded_msg_requests.has_value()) { + return *blinded_msg_requests; + } + return true; +} + +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_UserProfile_setCommunityMessageRequests( + JNIEnv *env, jobject thiz, jboolean blocks) { + std::lock_guard lock{util::util_mutex_}; + auto profile = ptrToProfile(env, thiz); + profile->set_blinded_msgreqs(std::optional{(bool)blocks}); +} +extern "C" +JNIEXPORT jboolean JNICALL +Java_network_loki_messenger_libsession_1util_UserProfile_isBlockCommunityMessageRequestsSet( + JNIEnv *env, jobject thiz) { + std::lock_guard lock{util::util_mutex_}; + auto profile = ptrToProfile(env, thiz); + return profile->get_blinded_msgreqs().has_value(); } \ No newline at end of file diff --git a/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt b/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt index 52fb541d7..7dc41d7fd 100644 --- a/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt +++ b/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt @@ -126,6 +126,9 @@ class UserProfile(pointer: Long) : ConfigBase(pointer) { external fun setPic(userPic: UserPic) external fun setNtsPriority(priority: Int) external fun getNtsPriority(): Int + external fun getCommunityMessageRequests(): Boolean + external fun setCommunityMessageRequests(blocks: Boolean) + external fun isBlockCommunityMessageRequestsSet(): Boolean } class ConversationVolatileConfig(pointer: Long): ConfigBase(pointer) { 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 4fff83383..1de94cfe3 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -42,6 +42,7 @@ interface StorageProtocol { fun getUserProfile(): Profile fun setProfileAvatar(recipient: Recipient, profileAvatar: String?) fun setProfilePicture(recipient: Recipient, newProfilePicture: String?, newProfileKey: ByteArray?) + fun setBlocksCommunityMessageRequests(recipient: Recipient, blocksMessageRequests: Boolean) fun setUserProfilePicture(newProfilePicture: String?, newProfileKey: ByteArray?) fun clearUserPic() // Signal @@ -228,4 +229,5 @@ interface StorageProtocol { fun notifyConfigUpdates(forConfigObject: ConfigBase) fun conversationInConfig(publicKey: String?, groupPublicKey: String?, openGroupId: String?, visibleOnly: Boolean): Boolean fun canPerformConfigChange(variant: String, publicKey: String, changeTimestampMs: Long): Boolean + fun isCheckingCommunityRequests(): Boolean } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt index 705fc7ce4..d6c067930 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt @@ -25,7 +25,8 @@ class VisibleMessage( var profile: Profile? = null, var openGroupInvitation: OpenGroupInvitation? = null, var reaction: Reaction? = null, - var hasMention: Boolean = false + var hasMention: Boolean = false, + var blocksMessageRequests: Boolean = false ) : Message() { override val isSelfSendValid: Boolean = true @@ -74,6 +75,9 @@ class VisibleMessage( val reaction = Reaction.fromProto(reactionProto) result.reaction = reaction } + + result.blocksMessageRequests = with (dataMessage) { hasBlocksCommunityMessageRequests() && blocksCommunityMessageRequests } + return result } } @@ -141,6 +145,8 @@ class VisibleMessage( return null } } + // Community blocked message requests flag + dataMessage.blocksCommunityMessageRequests = blocksMessageRequests // Sync target if (syncTarget != null) { dataMessage.syncTarget = syncTarget diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt index dc6d1475f..c6b6186c9 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt @@ -753,7 +753,8 @@ object OpenGroupApi { ) } val serverCapabilities = storage.getServerCapabilities(server) - if (serverCapabilities.contains(Capability.BLIND.name.lowercase())) { + val isAcceptingCommunityRequests = storage.isCheckingCommunityRequests() + if (serverCapabilities.contains(Capability.BLIND.name.lowercase()) && isAcceptingCommunityRequests) { requests.add( if (lastInboxMessageId == null) { BatchRequestInfo( 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 be7075dc5..e7929bc35 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 @@ -242,9 +242,16 @@ object MessageSender { private fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise { val deferred = deferred() val storage = MessagingModuleConfiguration.shared.storage + val configFactory = MessagingModuleConfiguration.shared.configFactory if (message.sentTimestamp == null) { message.sentTimestamp = SnodeAPI.nowWithOffset } + // Attach the blocks message requests info + configFactory.user?.let { user -> + if (message is VisibleMessage) { + message.blocksMessageRequests = !user.getCommunityMessageRequests() + } + } val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair()!! var serverCapabilities = listOf() var blindedPublicKey: ByteArray? = null 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 b101e9db0..8745418e7 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 @@ -304,6 +304,10 @@ fun MessageReceiver.handleVisibleMessage( profileManager.setProfilePicture(context, recipient, null, null) } } + + if (userPublicKey != messageSender && !isUserBlindedSender) { + storage.setBlocksCommunityMessageRequests(recipient, message.blocksMessageRequests) + } } // Parse quote if needed var quoteModel: QuoteModel? = null diff --git a/libsession/src/main/java/org/session/libsession/utilities/GroupUtil.kt b/libsession/src/main/java/org/session/libsession/utilities/GroupUtil.kt index bfab2585d..5d6741d30 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/GroupUtil.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/GroupUtil.kt @@ -1,6 +1,7 @@ package org.session.libsession.utilities -import network.loki.messenger.libsession_util.util.GroupInfo +import org.session.libsession.messaging.open_groups.OpenGroup +import org.session.libsession.messaging.utilities.SessionId import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.utilities.Hex import java.io.IOException @@ -16,8 +17,15 @@ object GroupUtil { } @JvmStatic - fun getEncodedOpenGroupInboxID(groupInboxID: ByteArray): String { - return OPEN_GROUP_INBOX_PREFIX + Hex.toStringCondensed(groupInboxID) + fun getEncodedOpenGroupInboxID(openGroup: OpenGroup, sessionId: SessionId): Address { + val openGroupInboxId = + "${openGroup.server}!${openGroup.publicKey}!${sessionId.hexString}".toByteArray() + return getEncodedOpenGroupInboxID(openGroupInboxId) + } + + @JvmStatic + fun getEncodedOpenGroupInboxID(groupInboxID: ByteArray): Address { + return Address.fromSerialized(OPEN_GROUP_INBOX_PREFIX + Hex.toStringCondensed(groupInboxID)) } @JvmStatic @@ -52,7 +60,7 @@ object GroupUtil { } @JvmStatic - fun getDecodedOpenGroupInbox(groupID: String): String { + fun getDecodedOpenGroupInboxSessionId(groupID: String): String { val decodedGroupId = getDecodedGroupID(groupID) if (decodedGroupId.split("!").count() > 2) { return decodedGroupId.split("!", limit = 3)[2] diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 2b92b9836..9f00047ac 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -287,6 +287,8 @@ interface TextSecurePreferences { const val OCEAN_DARK = "ocean.dark" const val OCEAN_LIGHT = "ocean.light" + const val ALLOW_MESSAGE_REQUESTS = "libsession.ALLOW_MESSAGE_REQUESTS" + @JvmStatic fun getLastConfigurationSyncTime(context: Context): Long { return getLongPreference(context, LAST_CONFIGURATION_SYNC_TIME, 0) diff --git a/libsession/src/main/java/org/session/libsession/utilities/recipients/Recipient.java b/libsession/src/main/java/org/session/libsession/utilities/recipients/Recipient.java index e2d193a93..20f285b59 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/recipients/Recipient.java +++ b/libsession/src/main/java/org/session/libsession/utilities/recipients/Recipient.java @@ -100,6 +100,7 @@ public class Recipient implements RecipientModifiedListener { private String notificationChannel; private boolean forceSmsSelection; private String wrapperHash; + private boolean blocksCommunityMessageRequests; private @NonNull UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.ENABLED; @@ -192,6 +193,7 @@ public class Recipient implements RecipientModifiedListener { this.unidentifiedAccessMode = details.get().unidentifiedAccessMode; this.forceSmsSelection = details.get().forceSmsSelection; this.notifyType = details.get().notifyType; + this.blocksCommunityMessageRequests = details.get().blocksCommunityMessageRequests; this.participants.clear(); this.participants.addAll(details.get().participants); @@ -228,6 +230,7 @@ public class Recipient implements RecipientModifiedListener { Recipient.this.unidentifiedAccessMode = result.unidentifiedAccessMode; Recipient.this.forceSmsSelection = result.forceSmsSelection; Recipient.this.notifyType = result.notifyType; + Recipient.this.blocksCommunityMessageRequests = result.blocksCommunityMessageRequests; Recipient.this.participants.clear(); Recipient.this.participants.addAll(result.participants); @@ -281,6 +284,7 @@ public class Recipient implements RecipientModifiedListener { this.unidentifiedAccessMode = details.unidentifiedAccessMode; this.forceSmsSelection = details.forceSmsSelection; this.wrapperHash = details.wrapperHash; + this.blocksCommunityMessageRequests = details.blocksCommunityMessageRequests; this.participants.addAll(details.participants); this.resolving = false; @@ -321,7 +325,7 @@ public class Recipient implements RecipientModifiedListener { return this.name; } } else if (isOpenGroupInboxRecipient()){ - String inboxID = GroupUtil.getDecodedOpenGroupInbox(sessionID); + String inboxID = GroupUtil.getDecodedOpenGroupInboxSessionId(sessionID); Contact contact = storage.getContactWithSessionID(inboxID); if (contact == null) { return sessionID; } return contact.displayName(Contact.ContactContext.REGULAR); @@ -345,6 +349,18 @@ public class Recipient implements RecipientModifiedListener { if (notify) notifyListeners(); } + public boolean getBlocksCommunityMessageRequests() { + return blocksCommunityMessageRequests; + } + + public void setBlocksCommunityMessageRequests(boolean blocksCommunityMessageRequests) { + synchronized (this) { + this.blocksCommunityMessageRequests = blocksCommunityMessageRequests; + } + + notifyListeners(); + } + public synchronized @NonNull MaterialColor getColor() { if (isGroupRecipient()) return MaterialColor.GROUP; else if (color != null) return color; @@ -759,12 +775,43 @@ public class Recipient implements RecipientModifiedListener { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Recipient recipient = (Recipient) o; - return resolving == recipient.resolving && mutedUntil == recipient.mutedUntil && notifyType == recipient.notifyType && blocked == recipient.blocked && approved == recipient.approved && approvedMe == recipient.approvedMe && expireMessages == recipient.expireMessages && address.equals(recipient.address) && Objects.equals(name, recipient.name) && Objects.equals(customLabel, recipient.customLabel) && Objects.equals(groupAvatarId, recipient.groupAvatarId) && Arrays.equals(profileKey, recipient.profileKey) && Objects.equals(profileName, recipient.profileName) && Objects.equals(profileAvatar, recipient.profileAvatar) && Objects.equals(wrapperHash, recipient.wrapperHash); + return resolving == recipient.resolving + && mutedUntil == recipient.mutedUntil + && notifyType == recipient.notifyType + && blocked == recipient.blocked + && approved == recipient.approved + && approvedMe == recipient.approvedMe + && expireMessages == recipient.expireMessages + && address.equals(recipient.address) + && Objects.equals(name, recipient.name) + && Objects.equals(customLabel, recipient.customLabel) + && Objects.equals(groupAvatarId, recipient.groupAvatarId) + && Arrays.equals(profileKey, recipient.profileKey) + && Objects.equals(profileName, recipient.profileName) + && Objects.equals(profileAvatar, recipient.profileAvatar) + && Objects.equals(wrapperHash, recipient.wrapperHash) + && blocksCommunityMessageRequests == recipient.blocksCommunityMessageRequests; } @Override public int hashCode() { - int result = Objects.hash(address, name, customLabel, resolving, groupAvatarId, mutedUntil, notifyType, blocked, approved, approvedMe, expireMessages, profileName, profileAvatar, wrapperHash); + int result = Objects.hash( + address, + name, + customLabel, + resolving, + groupAvatarId, + mutedUntil, + notifyType, + blocked, + approved, + approvedMe, + expireMessages, + profileName, + profileAvatar, + wrapperHash, + blocksCommunityMessageRequests + ); result = 31 * result + Arrays.hashCode(profileKey); return result; } @@ -869,55 +916,59 @@ public class Recipient implements RecipientModifiedListener { private final UnidentifiedAccessMode unidentifiedAccessMode; private final boolean forceSmsSelection; private final String wrapperHash; + private final boolean blocksCommunityMessageRequests; public RecipientSettings(boolean blocked, boolean approved, boolean approvedMe, long muteUntil, - int notifyType, - @NonNull VibrateState messageVibrateState, - @NonNull VibrateState callVibrateState, - @Nullable Uri messageRingtone, - @Nullable Uri callRingtone, - @Nullable MaterialColor color, - int defaultSubscriptionId, - int expireMessages, - @NonNull RegisteredState registered, - @Nullable byte[] profileKey, - @Nullable String systemDisplayName, - @Nullable String systemContactPhoto, - @Nullable String systemPhoneLabel, - @Nullable String systemContactUri, - @Nullable String signalProfileName, - @Nullable String signalProfileAvatar, - boolean profileSharing, - @Nullable String notificationChannel, - @NonNull UnidentifiedAccessMode unidentifiedAccessMode, - boolean forceSmsSelection, - String wrapperHash) + int notifyType, + @NonNull VibrateState messageVibrateState, + @NonNull VibrateState callVibrateState, + @Nullable Uri messageRingtone, + @Nullable Uri callRingtone, + @Nullable MaterialColor color, + int defaultSubscriptionId, + int expireMessages, + @NonNull RegisteredState registered, + @Nullable byte[] profileKey, + @Nullable String systemDisplayName, + @Nullable String systemContactPhoto, + @Nullable String systemPhoneLabel, + @Nullable String systemContactUri, + @Nullable String signalProfileName, + @Nullable String signalProfileAvatar, + boolean profileSharing, + @Nullable String notificationChannel, + @NonNull UnidentifiedAccessMode unidentifiedAccessMode, + boolean forceSmsSelection, + String wrapperHash, + boolean blocksCommunityMessageRequests + ) { - this.blocked = blocked; - this.approved = approved; - this.approvedMe = approvedMe; - this.muteUntil = muteUntil; - this.notifyType = notifyType; - this.messageVibrateState = messageVibrateState; - this.callVibrateState = callVibrateState; - this.messageRingtone = messageRingtone; - this.callRingtone = callRingtone; - this.color = color; - this.defaultSubscriptionId = defaultSubscriptionId; - this.expireMessages = expireMessages; - this.registered = registered; - this.profileKey = profileKey; - this.systemDisplayName = systemDisplayName; - this.systemContactPhoto = systemContactPhoto; - this.systemPhoneLabel = systemPhoneLabel; - this.systemContactUri = systemContactUri; - this.signalProfileName = signalProfileName; - this.signalProfileAvatar = signalProfileAvatar; - this.profileSharing = profileSharing; - this.notificationChannel = notificationChannel; - this.unidentifiedAccessMode = unidentifiedAccessMode; - this.forceSmsSelection = forceSmsSelection; - this.wrapperHash = wrapperHash; + this.blocked = blocked; + this.approved = approved; + this.approvedMe = approvedMe; + this.muteUntil = muteUntil; + this.notifyType = notifyType; + this.messageVibrateState = messageVibrateState; + this.callVibrateState = callVibrateState; + this.messageRingtone = messageRingtone; + this.callRingtone = callRingtone; + this.color = color; + this.defaultSubscriptionId = defaultSubscriptionId; + this.expireMessages = expireMessages; + this.registered = registered; + this.profileKey = profileKey; + this.systemDisplayName = systemDisplayName; + this.systemContactPhoto = systemContactPhoto; + this.systemPhoneLabel = systemPhoneLabel; + this.systemContactUri = systemContactUri; + this.signalProfileName = signalProfileName; + this.signalProfileAvatar = signalProfileAvatar; + this.profileSharing = profileSharing; + this.notificationChannel = notificationChannel; + this.unidentifiedAccessMode = unidentifiedAccessMode; + this.forceSmsSelection = forceSmsSelection; + this.wrapperHash = wrapperHash; + this.blocksCommunityMessageRequests = blocksCommunityMessageRequests; } public @Nullable MaterialColor getColor() { @@ -1020,6 +1071,10 @@ public class Recipient implements RecipientModifiedListener { return wrapperHash; } + public boolean getBlocksCommunityMessageRequests() { + return blocksCommunityMessageRequests; + } + } diff --git a/libsession/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java b/libsession/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java index 75ebd837b..195250cb3 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java +++ b/libsession/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java @@ -178,6 +178,7 @@ class RecipientProvider { @NonNull final UnidentifiedAccessMode unidentifiedAccessMode; final boolean forceSmsSelection; final String wrapperHash; + final boolean blocksCommunityMessageRequests; RecipientDetails(@Nullable String name, @Nullable Long groupAvatarId, boolean systemContact, boolean isLocalNumber, @Nullable RecipientSettings settings, @@ -211,6 +212,7 @@ class RecipientProvider { this.unidentifiedAccessMode = settings != null ? settings.getUnidentifiedAccessMode() : UnidentifiedAccessMode.DISABLED; this.forceSmsSelection = settings != null && settings.isForceSmsSelection(); this.wrapperHash = settings != null ? settings.getWrapperHash() : null; + this.blocksCommunityMessageRequests = settings != null && settings.getBlocksCommunityMessageRequests(); if (name == null && settings != null) this.name = settings.getSystemDisplayName(); else this.name = name; diff --git a/libsignal/protobuf/SignalService.proto b/libsignal/protobuf/SignalService.proto index 68dd35ce6..a2448604f 100644 --- a/libsignal/protobuf/SignalService.proto +++ b/libsignal/protobuf/SignalService.proto @@ -163,20 +163,21 @@ message DataMessage { required Action action = 4; } - optional string body = 1; - repeated AttachmentPointer attachments = 2; - optional GroupContext group = 3; - optional uint32 flags = 4; - optional uint32 expireTimer = 5; - optional bytes profileKey = 6; - optional uint64 timestamp = 7; - optional Quote quote = 8; - repeated Preview preview = 10; - optional Reaction reaction = 11; - optional LokiProfile profile = 101; - optional OpenGroupInvitation openGroupInvitation = 102; - optional ClosedGroupControlMessage closedGroupControlMessage = 104; - optional string syncTarget = 105; + optional string body = 1; + repeated AttachmentPointer attachments = 2; + optional GroupContext group = 3; + optional uint32 flags = 4; + optional uint32 expireTimer = 5; + optional bytes profileKey = 6; + optional uint64 timestamp = 7; + optional Quote quote = 8; + repeated Preview preview = 10; + optional Reaction reaction = 11; + optional LokiProfile profile = 101; + optional OpenGroupInvitation openGroupInvitation = 102; + optional ClosedGroupControlMessage closedGroupControlMessage = 104; + optional string syncTarget = 105; + optional bool blocksCommunityMessageRequests = 106; } message CallMessage { 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 8e26b05d9..895575b09 100644 --- a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java +++ b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java @@ -5890,6 +5890,16 @@ public final class SignalServiceProtos { */ com.google.protobuf.ByteString getSyncTargetBytes(); + + // optional bool blocksCommunityMessageRequests = 106; + /** + * optional bool blocksCommunityMessageRequests = 106; + */ + boolean hasBlocksCommunityMessageRequests(); + /** + * optional bool blocksCommunityMessageRequests = 106; + */ + boolean getBlocksCommunityMessageRequests(); } /** * Protobuf type {@code signalservice.DataMessage} @@ -6066,6 +6076,11 @@ public final class SignalServiceProtos { syncTarget_ = input.readBytes(); break; } + case 848: { + bitField0_ |= 0x00001000; + blocksCommunityMessageRequests_ = input.readBool(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -14336,6 +14351,22 @@ public final class SignalServiceProtos { } } + // optional bool blocksCommunityMessageRequests = 106; + public static final int BLOCKSCOMMUNITYMESSAGEREQUESTS_FIELD_NUMBER = 106; + private boolean blocksCommunityMessageRequests_; + /** + * optional bool blocksCommunityMessageRequests = 106; + */ + public boolean hasBlocksCommunityMessageRequests() { + return ((bitField0_ & 0x00001000) == 0x00001000); + } + /** + * optional bool blocksCommunityMessageRequests = 106; + */ + public boolean getBlocksCommunityMessageRequests() { + return blocksCommunityMessageRequests_; + } + private void initFields() { body_ = ""; attachments_ = java.util.Collections.emptyList(); @@ -14351,6 +14382,7 @@ public final class SignalServiceProtos { openGroupInvitation_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance(); closedGroupControlMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance(); syncTarget_ = ""; + blocksCommunityMessageRequests_ = false; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -14448,6 +14480,9 @@ public final class SignalServiceProtos { if (((bitField0_ & 0x00000800) == 0x00000800)) { output.writeBytes(105, getSyncTargetBytes()); } + if (((bitField0_ & 0x00001000) == 0x00001000)) { + output.writeBool(106, blocksCommunityMessageRequests_); + } getUnknownFields().writeTo(output); } @@ -14513,6 +14548,10 @@ public final class SignalServiceProtos { size += com.google.protobuf.CodedOutputStream .computeBytesSize(105, getSyncTargetBytes()); } + if (((bitField0_ & 0x00001000) == 0x00001000)) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(106, blocksCommunityMessageRequests_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -14697,6 +14736,8 @@ public final class SignalServiceProtos { bitField0_ = (bitField0_ & ~0x00001000); syncTarget_ = ""; bitField0_ = (bitField0_ & ~0x00002000); + blocksCommunityMessageRequests_ = false; + bitField0_ = (bitField0_ & ~0x00004000); return this; } @@ -14815,6 +14856,10 @@ public final class SignalServiceProtos { to_bitField0_ |= 0x00000800; } result.syncTarget_ = syncTarget_; + if (((from_bitField0_ & 0x00004000) == 0x00004000)) { + to_bitField0_ |= 0x00001000; + } + result.blocksCommunityMessageRequests_ = blocksCommunityMessageRequests_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -14923,6 +14968,9 @@ public final class SignalServiceProtos { syncTarget_ = other.syncTarget_; onChanged(); } + if (other.hasBlocksCommunityMessageRequests()) { + setBlocksCommunityMessageRequests(other.getBlocksCommunityMessageRequests()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -16457,6 +16505,39 @@ public final class SignalServiceProtos { return this; } + // optional bool blocksCommunityMessageRequests = 106; + private boolean blocksCommunityMessageRequests_ ; + /** + * optional bool blocksCommunityMessageRequests = 106; + */ + public boolean hasBlocksCommunityMessageRequests() { + return ((bitField0_ & 0x00004000) == 0x00004000); + } + /** + * optional bool blocksCommunityMessageRequests = 106; + */ + public boolean getBlocksCommunityMessageRequests() { + return blocksCommunityMessageRequests_; + } + /** + * optional bool blocksCommunityMessageRequests = 106; + */ + public Builder setBlocksCommunityMessageRequests(boolean value) { + bitField0_ |= 0x00004000; + blocksCommunityMessageRequests_ = value; + onChanged(); + return this; + } + /** + * optional bool blocksCommunityMessageRequests = 106; + */ + public Builder clearBlocksCommunityMessageRequests() { + bitField0_ = (bitField0_ & ~0x00004000); + blocksCommunityMessageRequests_ = false; + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:signalservice.DataMessage) } @@ -27160,7 +27241,7 @@ public final class SignalServiceProtos { "actionNotification\022<\n\004type\030\001 \002(\0162..signa" + "lservice.DataExtractionNotification.Type" + "\022\021\n\ttimestamp\030\002 \001(\004\"\'\n\004Type\022\016\n\nSCREENSHO" + - "T\020\001\022\017\n\013MEDIA_SAVED\020\002\"\361\r\n\013DataMessage\022\014\n\004", + "T\020\001\022\017\n\013MEDIA_SAVED\020\002\"\231\016\n\013DataMessage\022\014\n\004", "body\030\001 \001(\t\0225\n\013attachments\030\002 \003(\0132 .signal" + "service.AttachmentPointer\022*\n\005group\030\003 \001(\013" + "2\033.signalservice.GroupContext\022\r\n\005flags\030\004" + @@ -27175,12 +27256,13 @@ public final class SignalServiceProtos { "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" + + "sage\022\022\n\nsyncTarget\030i \001(\t\022&\n\036blocksCommun" + + "ityMessageRequests\030j \001(\010\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", + "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 " + @@ -27189,8 +27271,8 @@ public final class SignalServiceProtos { "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", + "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" + @@ -27199,8 +27281,8 @@ public final class SignalServiceProtos { "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\032\222\001\n\010Reaction\022\n\n\002id\030\001 \002(\004\022\016", + "MEMBERS_ADDED\020\005\022\023\n\017MEMBERS_REMOVED\020\006\022\017\n\013", + "MEMBER_LEFT\020\007\032\222\001\n\010Reaction\022\n\n\002id\030\001 \002(\004\022\016" + "\n\006author\030\002 \002(\t\022\r\n\005emoji\030\003 \001(\t\022:\n\006action\030" + "\004 \002(\0162*.signalservice.DataMessage.Reacti" + "on.Action\"\037\n\006Action\022\t\n\005REACT\020\000\022\n\n\006REMOVE" + @@ -27209,8 +27291,8 @@ public final class SignalServiceProtos { "ervice.CallMessage.Type\022\014\n\004sdps\030\002 \003(\t\022\027\n" + "\017sdpMLineIndexes\030\003 \003(\r\022\017\n\007sdpMids\030\004 \003(\t\022" + "\014\n\004uuid\030\005 \002(\t\"f\n\004Type\022\r\n\tPRE_OFFER\020\006\022\t\n\005" + - "OFFER\020\001\022\n\n\006ANSWER\020\002\022\026\n\022PROVISIONAL_ANSWE" + - "R\020\003\022\022\n\016ICE_CANDIDATES\020\004\022\014\n\010END_CALL\020\005\"\245\004", + "OFFER\020\001\022\n\n\006ANSWER\020\002\022\026\n\022PROVISIONAL_ANSWE", + "R\020\003\022\022\n\016ICE_CANDIDATES\020\004\022\014\n\010END_CALL\020\005\"\245\004" + "\n\024ConfigurationMessage\022E\n\014closedGroups\030\001" + " \003(\0132/.signalservice.ConfigurationMessag" + "e.ClosedGroup\022\022\n\nopenGroups\030\002 \003(\t\022\023\n\013dis" + @@ -27219,8 +27301,8 @@ public final class SignalServiceProtos { "ignalservice.ConfigurationMessage.Contac" + "t\032\233\001\n\013ClosedGroup\022\021\n\tpublicKey\030\001 \001(\014\022\014\n\004" + "name\030\002 \001(\t\0221\n\021encryptionKeyPair\030\003 \001(\0132\026." + - "signalservice.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\032", + "signalservice.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\032" + "\223\001\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\nprofileK" + "ey\030\004 \001(\014\022\022\n\nisApproved\030\005 \001(\010\022\021\n\tisBlocke" + @@ -27229,8 +27311,8 @@ public final class SignalServiceProtos { "rofileKey\030\002 \001(\014\0227\n\007profile\030\003 \001(\0132&.signa" + "lservice.DataMessage.LokiProfile\"\375\001\n\023Sha" + "redConfigMessage\0225\n\004kind\030\001 \002(\0162\'.signals" + - "ervice.SharedConfigMessage.Kind\022\r\n\005seqno" + - "\030\002 \002(\003\022\014\n\004data\030\003 \002(\014\"\221\001\n\004Kind\022\020\n\014USER_PR", + "ervice.SharedConfigMessage.Kind\022\r\n\005seqno", + "\030\002 \002(\003\022\014\n\004data\030\003 \002(\014\"\221\001\n\004Kind\022\020\n\014USER_PR" + "OFILE\020\001\022\014\n\010CONTACTS\020\002\022\027\n\023CONVO_INFO_VOLA" + "TILE\020\003\022\n\n\006GROUPS\020\004\022\025\n\021CLOSED_GROUP_INFO\020" + "\005\022\030\n\024CLOSED_GROUP_MEMBERS\020\006\022\023\n\017ENCRYPTIO" + @@ -27239,8 +27321,8 @@ public final class SignalServiceProtos { "timestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010DELIVERY\020\000\022\010\n" + "\004READ\020\001\"\354\001\n\021AttachmentPointer\022\n\n\002id\030\001 \002(" + "\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004s" + - "ize\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\005flags\030\010 \001(\r\022\r\n", + "ize\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\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_MES" + "SAGE\020\001\"\365\001\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022.\n\004t" + @@ -27249,7 +27331,7 @@ public final class SignalServiceProtos { "atar\030\005 \001(\0132 .signalservice.AttachmentPoi" + "nter\022\016\n\006admins\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\014" + - "REQUEST_INFO\020\004B3\n\034org.session.libsignal." + + "REQUEST_INFO\020\004B3\n\034org.session.libsignal.", "protosB\023SignalServiceProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = @@ -27298,7 +27380,7 @@ public final class SignalServiceProtos { internal_static_signalservice_DataMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_descriptor, - new java.lang.String[] { "Body", "Attachments", "Group", "Flags", "ExpireTimer", "ProfileKey", "Timestamp", "Quote", "Preview", "Reaction", "Profile", "OpenGroupInvitation", "ClosedGroupControlMessage", "SyncTarget", }); + new java.lang.String[] { "Body", "Attachments", "Group", "Flags", "ExpireTimer", "ProfileKey", "Timestamp", "Quote", "Preview", "Reaction", "Profile", "OpenGroupInvitation", "ClosedGroupControlMessage", "SyncTarget", "BlocksCommunityMessageRequests", }); internal_static_signalservice_DataMessage_Quote_descriptor = internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(0); internal_static_signalservice_DataMessage_Quote_fieldAccessorTable = new