[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
This commit is contained in:
0x330a 2023-08-28 09:51:48 +10:00 committed by GitHub
parent f6345c86ce
commit 2466d9b4c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 522 additions and 157 deletions

View File

@ -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

View File

@ -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

View File

@ -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<OpenGroup> = 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 <T : ViewModel> create(modelClass: Class<T>): T {
return ConversationViewModel(threadId, edKeyPair, contentResolver, repository, storage) as T
return ConversationViewModel(threadId, edKeyPair, repository, storage) as T
}
}
}

View File

@ -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<String> 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();

View File

@ -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 {

View File

@ -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();

View File

@ -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
}

View File

@ -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)
}
}
}
}

View File

@ -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) {

View File

@ -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<Preference>(TextSecurePreferences.SCREEN_LOCK)!!
@ -30,6 +39,33 @@ class PrivacySettingsPreferenceFragment : ListSummaryPreferenceFragment() {
.onPreferenceChangeListener = TypingIndicatorsToggleListener()
findPreference<Preference>(TextSecurePreferences.CALL_NOTIFICATIONS_ENABLED)!!
.onPreferenceChangeListener = CallToggleListener(this) { setCall(it) }
findPreference<PreferenceCategory>(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()
}

View File

@ -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<Recipient?>
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<Recipient?> {
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()

View File

@ -627,6 +627,9 @@
<string name="preferences_notifications__priority">Priority</string>
<string name="preferences_app_protection__screenshot_notifications">Screenshot Notifications</string>
<string name="preferences_app_protected__screenshot_notifications_summary">Receive a notification when a contact takes a screenshot of a one-to-one chat.</string>
<string name="preferences__message_requests_category">Message Requests</string>
<string name="preferences__message_requests_title">Community Message Requests</string>
<string name="preferences__message_requests_summary">Allow message requests from Community conversations</string>
<!-- **************************************** -->
<!-- menus -->
<!-- **************************************** -->
@ -1033,6 +1036,7 @@
<string name="activity_home_outdated_client_config">Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.</string>
<string name="activity_conversation_empty_state_read_only">There are no messages in <b>%s</b>.</string>
<string name="activity_conversation_empty_state_blocks_community_requests"><b>%s</b> has message requests from Community conversations turned off, so you cannot send them a message.</string>
<string name="activity_conversation_empty_state_note_to_self">You have no messages in Note to Self.</string>
<string name="activity_conversation_empty_state_default">You have no messages from <b>%s</b>.\nSend a message to start the conversation!</string>

View File

@ -20,6 +20,12 @@
</PreferenceCategory>
<PreferenceCategory
android:title="@string/preferences__message_requests_category"
android:key="@string/preferences__message_requests_category"
android:persistent="false">
</PreferenceCategory>
<PreferenceCategory android:title="@string/preferences__read_receipts">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"

View File

@ -1,42 +1,46 @@
package org.thoughtcrime.securesms.conversation.v2
import com.goterl.lazysodium.utils.KeyPair
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.first
import org.hamcrest.CoreMatchers.endsWith
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.CoreMatchers.notNullValue
import org.hamcrest.CoreMatchers.nullValue
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.anySet
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.BaseViewModelTest
import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.repository.ConversationRepository
import org.thoughtcrime.securesms.repository.ResultOf
import org.mockito.Mockito.`when` as whenever
class ConversationViewModelTest: BaseViewModelTest() {
private val repository = mock(ConversationRepository::class.java)
private val storage = mock(Storage::class.java)
private val repository = mock<ConversationRepository>()
private val storage = mock<Storage>()
private val threadId = 123L
private val edKeyPair = mock(KeyPair::class.java)
private val edKeyPair = mock<KeyPair>()
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<MessageRecord>()
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<MessageRecord>()
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<MessageRecord>()
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<Recipient> {
whenever(it.blocksCommunityMessageRequests).thenReturn(true)
}
whenever(repository.maybeGetBlindedRecipient(recipient)).thenReturn(blinded)
assertThat(viewModel.blindedRecipient, notNullValue())
assertThat(viewModel.hidesInputBar(), equalTo(true))
}
}

@ -1 +1 @@
Subproject commit 7eb87028355bfc89950102c52d5b2927a25b2e22
Subproject commit e3ccf29db08aaf0b9bb6bbe72ae5967cd183a78d

View File

@ -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();
}

View File

@ -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) {

View File

@ -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
}

View File

@ -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

View File

@ -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(

View File

@ -242,9 +242,16 @@ object MessageSender {
private fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise<Unit, Exception> {
val deferred = deferred<Unit, Exception>()
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<String>()
var blindedPublicKey: ByteArray? = null

View File

@ -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

View File

@ -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]

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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 {

View File

@ -5890,6 +5890,16 @@ public final class SignalServiceProtos {
*/
com.google.protobuf.ByteString
getSyncTargetBytes();
// optional bool blocksCommunityMessageRequests = 106;
/**
* <code>optional bool blocksCommunityMessageRequests = 106;</code>
*/
boolean hasBlocksCommunityMessageRequests();
/**
* <code>optional bool blocksCommunityMessageRequests = 106;</code>
*/
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_;
/**
* <code>optional bool blocksCommunityMessageRequests = 106;</code>
*/
public boolean hasBlocksCommunityMessageRequests() {
return ((bitField0_ & 0x00001000) == 0x00001000);
}
/**
* <code>optional bool blocksCommunityMessageRequests = 106;</code>
*/
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_ ;
/**
* <code>optional bool blocksCommunityMessageRequests = 106;</code>
*/
public boolean hasBlocksCommunityMessageRequests() {
return ((bitField0_ & 0x00004000) == 0x00004000);
}
/**
* <code>optional bool blocksCommunityMessageRequests = 106;</code>
*/
public boolean getBlocksCommunityMessageRequests() {
return blocksCommunityMessageRequests_;
}
/**
* <code>optional bool blocksCommunityMessageRequests = 106;</code>
*/
public Builder setBlocksCommunityMessageRequests(boolean value) {
bitField0_ |= 0x00004000;
blocksCommunityMessageRequests_ = value;
onChanged();
return this;
}
/**
* <code>optional bool blocksCommunityMessageRequests = 106;</code>
*/
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