Allow opting out of key transparency.

This commit is contained in:
Michelle Tang
2026-01-30 13:48:01 -05:00
committed by Greyson Parrelli
parent 423b8c942c
commit a11888ff71
11 changed files with 98 additions and 5 deletions

View File

@@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.compose.rememberStatusBarColorNestedScrollModifier
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.viewModel
/**
@@ -152,6 +153,17 @@ class AdvancedPrivacySettingsFragment : ComposeFragment() {
getString(R.string.AdvancedPrivacySettingsFragment__sealed_sender_link)
)
}
override fun onAllowAutomaticVerificationChanged(enabled: Boolean) {
viewModel.setAllowAutomaticVerification(enabled)
}
override fun onAutomaticVerificationLearnMoreClick() {
CommunicationActions.openBrowserLink(
requireContext(),
getString(R.string.verify_display_fragment__link)
)
}
}
}
@@ -163,6 +175,8 @@ private interface AdvancedPrivacySettingsCallbacks {
fun onShowStatusIconForSealedSenderChanged(enabled: Boolean) = Unit
fun onAllowSealedSenderFromAnyoneChanged(enabled: Boolean) = Unit
fun onSealedSenderLearnMoreClick() = Unit
fun onAutomaticVerificationLearnMoreClick() = Unit
fun onAllowAutomaticVerificationChanged(enabled: Boolean) = Unit
object Empty : AdvancedPrivacySettingsCallbacks
}
@@ -284,6 +298,33 @@ private fun AdvancedPrivacySettingsScreen(
text = sealedSenderSummary
)
}
if (RemoteConfig.keyTransparency) {
item {
Dividers.Default()
}
item {
val label = buildAnnotatedString {
append(stringResource(R.string.preferences_automatic_key_verification_body))
append(" ")
withLink(
LinkAnnotation.Clickable("learn-more", linkInteractionListener = {
callbacks.onAutomaticVerificationLearnMoreClick()
})
) {
append(stringResource(R.string.LearnMoreTextView_learn_more))
}
}
Rows.ToggleRow(
checked = state.allowAutomaticKeyVerification,
text = AnnotatedString(stringResource(R.string.preferences_automatic_key_verification)),
label = label,
onCheckChanged = callbacks::onAllowAutomaticVerificationChanged
)
}
}
}
}
}
@@ -300,7 +341,8 @@ private fun AdvancedPrivacySettingsScreenPreview() {
censorshipCircumventionEnabled = false,
showSealedSenderStatusIcon = false,
allowSealedSenderFromAnyone = false,
showProgressSpinner = false
showProgressSpinner = false,
allowAutomaticKeyVerification = false
),
callbacks = AdvancedPrivacySettingsCallbacks.Empty
)

View File

@@ -7,7 +7,8 @@ data class AdvancedPrivacySettingsState(
val censorshipCircumventionEnabled: Boolean,
val showSealedSenderStatusIcon: Boolean,
val allowSealedSenderFromAnyone: Boolean,
val showProgressSpinner: Boolean
val showProgressSpinner: Boolean,
val allowAutomaticKeyVerification: Boolean
)
enum class CensorshipCircumventionState(val available: Boolean) {

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.settings.app.privacy.advanced
import android.content.SharedPreferences
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -9,12 +10,17 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.signal.core.util.concurrent.SignalDispatchers
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob
import org.thoughtcrime.securesms.keyvalue.SettingsValues
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.SignalE164Util
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
@@ -63,6 +69,18 @@ class AdvancedPrivacySettingsViewModel(
refresh()
}
fun setAllowAutomaticVerification(enabled: Boolean) {
SignalStore.settings.automaticVerificationEnabled = enabled
refresh()
viewModelScope.launch(SignalDispatchers.IO) {
if (!enabled) {
SignalDatabase.recipients.clearAllKeyTransparencyData()
}
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
StorageSyncHelper.scheduleSyncForDataChange()
}
}
fun refresh() {
store.update { getState().copy(showProgressSpinner = it.showProgressSpinner) }
}
@@ -85,7 +103,8 @@ class AdvancedPrivacySettingsViewModel(
allowSealedSenderFromAnyone = TextSecurePreferences.isUniversalUnidentifiedAccess(
AppDependencies.application
),
false
showProgressSpinner = false,
allowAutomaticKeyVerification = SignalStore.settings.automaticVerificationEnabled
)
}

View File

@@ -1418,7 +1418,7 @@ class ConversationFragment :
}
private fun presentVerifyAutomaticallySheet() {
if (RemoteConfig.keyTransparency && !SignalStore.uiHints.hasSeenVerifyAutomaticallySheet() && viewModel.recipientSnapshot?.isIndividual == true) {
if (RemoteConfig.keyTransparency && SignalStore.settings.automaticVerificationEnabled && !SignalStore.uiHints.hasSeenVerifyAutomaticallySheet() && viewModel.recipientSnapshot?.isIndividual == true) {
VerifyAutomaticallyEducationSheet.show(parentFragmentManager)
}
}

View File

@@ -4048,6 +4048,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
.run()
}
fun clearAllKeyTransparencyData() {
writableDatabase
.update(TABLE_NAME)
.values(KEY_TRANSPARENCY_DATA to null)
.where("$KEY_TRANSPARENCY_DATA IS NOT NULL")
.run()
}
/**
* Will update the database with the content values you specified. It will make an intelligent
* query such that this will only return true if a row was *actually* updated.

View File

@@ -75,6 +75,7 @@ public final class SettingsValues extends SignalStoreValues {
private static final String PASSPHRASE_TIMEOUT = "settings.passphrase.timeout";
private static final String SCREEN_LOCK_ENABLED = "settings.screen.lock.enabled";
private static final String SCREEN_LOCK_TIMEOUT = "settings.screen.lock.timeout";
private static final String AUTOMATIC_VERIFICATION_ENABLED = "settings.automatic.verification.enabled";
public static final int BACKUP_DEFAULT_HOUR = 2;
public static final int BACKUP_DEFAULT_MINUTE = 0;
@@ -560,6 +561,14 @@ public final class SettingsValues extends SignalStoreValues {
return getLong(SCREEN_LOCK_TIMEOUT, 0);
}
public boolean getAutomaticVerificationEnabled() {
return getBoolean(AUTOMATIC_VERIFICATION_ENABLED, true);
}
public void setAutomaticVerificationEnabled(boolean enabled) {
putBoolean(AUTOMATIC_VERIFICATION_ENABLED, enabled);
}
private @Nullable Uri getUri(@NonNull String key) {
String uri = getString(key, "");

View File

@@ -138,6 +138,7 @@ class AccountRecordProcessor(
usernameLink = remote.proto.usernameLink
notificationProfileManualOverride = remote.proto.notificationProfileManualOverride
backupTier = local.proto.backupTier ?: remote.proto.backupTier
automaticKeyVerificationDisabled = remote.proto.automaticKeyVerificationDisabled
safeSetPayments(payments?.enabled == true, payments?.entropy?.toByteArray())
safeSetSubscriber(donationSubscriberId, donationSubscriberCurrencyCode)

View File

@@ -195,6 +195,7 @@ object StorageSyncHelper {
}
safeSetPayments(SignalStore.payments.mobileCoinPaymentsEnabled(), Optional.ofNullable(SignalStore.payments.paymentsEntropy).map { obj: Entropy -> obj.bytes }.orElse(null))
automaticKeyVerificationDisabled = !SignalStore.settings.automaticVerificationEnabled
}
return accountRecord.toSignalAccountRecord(StorageId.forAccount(storageId)).toSignalStorageRecord()
@@ -258,6 +259,11 @@ object StorageSyncHelper {
SignalStore.story.userHasSeenGroupStoryEducationSheet = update.new.proto.hasSeenGroupStoryEducationSheet
SignalStore.uiHints.setHasCompletedUsernameOnboarding(update.new.proto.hasCompletedUsernameOnboarding)
if (SignalStore.settings.automaticVerificationEnabled && update.new.proto.automaticKeyVerificationDisabled) {
SignalDatabase.recipients.clearAllKeyTransparencyData()
}
SignalStore.settings.automaticVerificationEnabled = !update.new.proto.automaticKeyVerificationDisabled
if (update.new.proto.storyViewReceiptsEnabled == OptionalBool.UNSET) {
SignalStore.story.viewedReceiptsEnabled = update.new.proto.readReceipts
} else {

View File

@@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.components.ViewBinderDelegate
import org.thoughtcrime.securesms.components.verify.SafetyNumberQrView.Companion.getSegments
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable
import org.thoughtcrime.securesms.databinding.VerifyDisplayFragmentBinding
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.RemoteConfig
@@ -74,7 +75,7 @@ class VerifyDisplayFragment : Fragment() {
updateVerifyButton(requireArguments().getBoolean(VERIFIED_STATE, false), false)
binding.automaticVerification.visible = RemoteConfig.keyTransparency
binding.automaticVerification.visible = RemoteConfig.keyTransparency && SignalStore.settings.automaticVerificationEnabled
binding.safetyQrView.verifyButton.setOnClickListener { updateVerifyButton(!currentVerifiedState, true) }
binding.toolbar.setNavigationOnClickListener { requireActivity().onBackPressed() }
binding.toolbar.setTitle(R.string.AndroidManifest__verify_safety_number)

View File

@@ -4247,6 +4247,10 @@
<string name="preferences_compact">Compact</string>
<!-- Dialog message body explaining that we have to restart the app in order to apply the user\'s new language setting. -->
<string name="preferences_language_change_confirmation_message">The app will restart to apply the new language setting.</string>
<!-- Preference title for automatic key verification -->
<string name="preferences_automatic_key_verification">Automatic key verification</string>
<!-- Preference summary for automatic key verification -->
<string name="preferences_automatic_key_verification_body">When enabled, Signal will attempt to automatically verify the encryption of 1:1 chats.</string>
<string name="configurable_single_select__customize_option">Customize option</string>

View File

@@ -294,6 +294,8 @@ message AccountRecord {
optional AvatarColor avatarColor = 42;
BackupTierHistory backupTierHistory = 43;
NotificationProfileManualOverride notificationProfileManualOverride = 44;
bool notificationProfileSyncDisabled = 45;
bool automaticKeyVerificationDisabled = 46;
}
message StoryDistributionListRecord {