4S settings screen

This commit is contained in:
Valere 2020-07-10 20:09:15 +02:00 committed by Benoit Marty
parent 25bbe9c3d6
commit c57d41863f
16 changed files with 294 additions and 24 deletions

View file

@ -19,7 +19,11 @@ package im.vector.matrix.rx
import androidx.paging.PagedList
import im.vector.matrix.android.api.query.QueryStringValue
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.identity.ThreePid
@ -36,9 +40,11 @@ import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.functions.Function3
class RxSession(private val session: Session) {
@ -165,6 +171,50 @@ class RxSession(private val session: Session) {
session.widgetService().getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes)
}
}
data class SecretsSynchronisationInfo(
val isBackupSetup: Boolean = false,
val isCrossSigningEnabled: Boolean = false,
val isCrossSigningTrusted: Boolean = false,
val allPrivateKeysKnown: Boolean = false,
val megolmBackupAvailable: Boolean = false,
val megolmSecretKnown: Boolean = false,
val isMegolmKeyIn4S: Boolean = false
)
fun liveSecretSynchronisationInfo(): Observable<SecretsSynchronisationInfo> {
return Observable.combineLatest<List<UserAccountData>, Optional<MXCrossSigningInfo>, Optional<PrivateKeysInfo>, SecretsSynchronisationInfo>(
liveAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)),
liveCrossSigningInfo(session.myUserId),
liveCrossSigningPrivateKeys(),
Function3 { _, crossSigningInfo, pInfo ->
// first check if 4S is already setup
val is4SSetup = session.sharedSecretStorageService.isRecoverySetup()
val isCrossSigningEnabled = crossSigningInfo.getOrNull() != null
val isCrossSigningTrusted = crossSigningInfo.getOrNull()?.isTrusted() == true
val allPrivateKeysKnown = pInfo.getOrNull()?.master != null
&& pInfo.getOrNull()?.selfSigned != null
&& pInfo.getOrNull()?.user != null
val keysBackupService = session.cryptoService().keysBackupService()
val currentBackupVersion = keysBackupService.currentBackupVersion
val megolmBackupAvailable = currentBackupVersion != null
val savedBackupKey = keysBackupService.getKeyBackupRecoveryKeyInfo()
val megolmKeyKnown = savedBackupKey?.version == currentBackupVersion
SecretsSynchronisationInfo(
isBackupSetup = is4SSetup,
isCrossSigningEnabled = isCrossSigningEnabled,
isCrossSigningTrusted = isCrossSigningTrusted,
allPrivateKeysKnown = allPrivateKeysKnown,
megolmBackupAvailable = megolmBackupAvailable,
megolmSecretKnown = megolmKeyKnown,
isMegolmKeyIn4S = session.sharedSecretStorageService.isMegolmKeyInBackup()
)
}
)
.distinctUntilChanged()
}
}
fun Session.rx(): RxSession {

View file

@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.securestorage
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
@ -124,6 +125,13 @@ interface SharedSecretStorageService {
) is IntegrityResult.Success
}
fun isMegolmKeyInBackup(): Boolean {
return checkShouldBeAbleToAccessSecrets(
secretNames = listOf(KEYBACKUP_SECRET_SSSS_NAME),
keyId = null
) is IntegrityResult.Success
}
fun checkShouldBeAbleToAccessSecrets(secretNames: List<String>, keyId: String?): IntegrityResult
fun requestSecret(name: String, myOtherDeviceId: String)

View file

@ -71,8 +71,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
delay(1500)
cryptoStore.getOrAddOutgoingSecretShareRequest(secretName, recipients)?.let {
// TODO check if there is already one that is being sent?
if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) {
Timber.v("## CRYPTO - GOSSIP sendSecretShareRequest() : we already request for that session: $it")
if (it.state == OutgoingGossipingRequestState.SENDING /**|| it.state == OutgoingGossipingRequestState.SENT*/) {
Timber.v("## CRYPTO - GOSSIP sendSecretShareRequest() : we are already sending for that session: $it")
return@launch
}

View file

@ -45,7 +45,8 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
@Parcelize
data class Args(
val initCrossSigningOnly: Boolean
val initCrossSigningOnly: Boolean,
val forceReset4S: Boolean
) : Parcelable
override val showExpanded = true
@ -180,10 +181,15 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
const val EXTRA_ARGS = "EXTRA_ARGS"
fun show(fragmentManager: FragmentManager, initCrossSigningOnly: Boolean) {
fun show(fragmentManager: FragmentManager, initCrossSigningOnly: Boolean, forceReset4S: Boolean) {
BootstrapBottomSheet().apply {
isCancelable = false
arguments = Bundle().apply { this.putParcelable(EXTRA_ARGS, Args(initCrossSigningOnly)) }
arguments = Bundle().apply {
this.putParcelable(EXTRA_ARGS, Args(
initCrossSigningOnly,
forceReset4S
))
}
}.show(fragmentManager, "BootstrapBottomSheet")
}
}

View file

@ -258,6 +258,30 @@ class BootstrapCrossSigningTask @Inject constructor(
)
}
}
} else {
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Existing megolm backup found")
// ensure we store existing backup secret if we have it!
val knownSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()
if (knownSecret != null && knownSecret.version == serverVersion.version) {
// check it matches
val isValid = awaitCallback<Boolean> {
session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownSecret.recoveryKey, it)
}
if (isValid) {
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known")
awaitCallback<Unit> {
extractCurveKeyFromRecoveryKey(knownSecret.recoveryKey)?.toBase64NoPadding()?.let { secret ->
ssssService.storeSecret(
KEYBACKUP_SECRET_SSSS_NAME,
secret,
listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), it
)
}
}
} else {
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key is unknown by this session")
}
}
}
} catch (failure: Throwable) {
Timber.e("## BootstrapCrossSigningTask: Failed to init keybackup")

View file

@ -58,6 +58,13 @@ class BootstrapSetupRecoveryKeyFragment @Inject constructor() : VectorBaseFragme
bootstrapSetupSecureUseSecurityPassphrase.isVisible = false
bootstrapSetupSecureUseSecurityPassphraseSeparator.isVisible = false
} else {
if (state.step.reset) {
bootstrapSetupSecureText.text = getString(R.string.reset_secure_backup_title)
bootstrapSetupWarningTextView.isVisible = true
} else {
bootstrapSetupSecureText.text = getString(R.string.bottom_sheet_setup_secure_backup_subtitle)
bootstrapSetupWarningTextView.isVisible = false
}
// Choose between create a passphrase or use a recovery key
bootstrapSetupSecureSubmit.isVisible = false
bootstrapSetupSecureUseSecurityKey.isVisible = true

View file

@ -69,7 +69,11 @@ class BootstrapSharedViewModel @AssistedInject constructor(
init {
if (args.initCrossSigningOnly) {
if (args.forceReset4S) {
setState {
copy(step = BootstrapStep.FirstForm(keyBackUpExist = false, reset = true))
}
} else if (args.initCrossSigningOnly) {
// Go straight to account password
setState {
copy(step = BootstrapStep.AccountPassword(false))
@ -554,7 +558,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
override fun create(viewModelContext: ViewModelContext, state: BootstrapViewState): BootstrapSharedViewModel? {
val fragment: BootstrapBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
val args: BootstrapBottomSheet.Args = fragment.arguments?.getParcelable(BootstrapBottomSheet.EXTRA_ARGS)
?: BootstrapBottomSheet.Args(initCrossSigningOnly = true)
?: BootstrapBottomSheet.Args(initCrossSigningOnly = true, forceReset4S = false)
return fragment.bootstrapViewModelFactory.create(state, args)
}
}

View file

@ -89,7 +89,7 @@ sealed class BootstrapStep {
object CheckingMigration : BootstrapStep()
// Use will be asked to choose between passphrase or recovery key, or to start process if a key backup exists
data class FirstForm(val keyBackUpExist: Boolean) : BootstrapStep()
data class FirstForm(val keyBackUpExist: Boolean, val reset: Boolean = false) : BootstrapStep()
data class SetupPassphrase(val isPasswordVisible: Boolean) : BootstrapStep()
data class ConfirmPassphrase(val isPasswordVisible: Boolean) : BootstrapStep()

View file

@ -146,7 +146,7 @@ class VerificationRequestController @Inject constructor(
}
}
if (state.isMe && state.currentDeviceCanCrossSign) {
if (state.isMe && state.currentDeviceCanCrossSign && !state.selfVerificationMode) {
dividerItem {
id("sep_notMe")
}

View file

@ -145,7 +145,7 @@ class DefaultNavigator @Inject constructor(
override fun upgradeSessionSecurity(context: Context, initCrossSigningOnly: Boolean) {
if (context is VectorBaseActivity) {
BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly)
BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly, false)
}
}
@ -221,7 +221,7 @@ class DefaultNavigator @Inject constructor(
// if cross signing is enabled we should propose full 4S
sessionHolder.getSafeActiveSession()?.let { session ->
if (session.cryptoService().crossSigningService().canCrossSign() && context is VectorBaseActivity) {
BootstrapBottomSheet.show(context.supportFragmentManager, false)
BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly = false, forceReset4S = false)
} else {
context.startActivity(KeysBackupSetupActivity.intent(context, showManualExport))
}

View file

@ -72,6 +72,9 @@ class VectorPreferences @Inject constructor(private val context: Context) {
const val SETTINGS_ALLOW_INTEGRATIONS_KEY = "SETTINGS_ALLOW_INTEGRATIONS_KEY"
const val SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY = "SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY"
const val SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY"
const val SETTINGS_CRYPTOGRAPHY_MANAGE_4S_CATEGORY_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_4S_CATEGORY_KEY"
const val SETTINGS_SECURE_BACKUP_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_BACKUP_RECOVERY_PREFERENCE_KEY"
// const val SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY = "SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY"
// user
const val SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY = "SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY"

View file

@ -33,6 +33,8 @@ import im.vector.matrix.android.internal.crypto.crosssigning.isVerified
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.rx.RxSession
import im.vector.matrix.rx.rx
import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.dialogs.ExportKeysDialog
@ -41,12 +43,17 @@ import im.vector.riotx.core.intent.ExternalIntentData
import im.vector.riotx.core.intent.analyseIntent
import im.vector.riotx.core.intent.getFilenameFromUri
import im.vector.riotx.core.platform.SimpleTextWatcher
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.preference.VectorPreference
import im.vector.riotx.core.preference.VectorPreferenceCategory
import im.vector.riotx.core.utils.openFileSelection
import im.vector.riotx.core.utils.toast
import im.vector.riotx.features.crypto.keys.KeysExporter
import im.vector.riotx.features.crypto.keys.KeysImporter
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.riotx.features.crypto.recover.BootstrapBottomSheet
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import javax.inject.Inject
class VectorSettingsSecurityPrivacyFragment @Inject constructor(
@ -56,6 +63,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
override var titleRes = R.string.settings_security_and_privacy
override val preferenceXmlRes = R.xml.vector_settings_security_privacy
private var disposables = emptyList<Disposable>().toMutableList()
// cryptography
private val mCryptographyCategory by lazy {
@ -92,6 +100,109 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
// My device name may have been updated
refreshMyDevice()
refreshXSigningStatus()
session.rx().liveSecretSynchronisationInfo()
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
refresh4SSection(it)
refreshXSigningStatus()
}.also {
disposables.add(it)
}
}
private val secureBackupCategory by lazy {
findPreference<VectorPreferenceCategory>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_MANAGE_4S_CATEGORY_KEY)
}
private val secureBackupPreference by lazy {
findPreference<VectorPreference>(VectorPreferences.SETTINGS_SECURE_BACKUP_RECOVERY_PREFERENCE_KEY)
}
// private val secureBackupResetPreference by lazy {
// findPreference<VectorPreference>(VectorPreferences.SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY)
// }
override fun onPause() {
super.onPause()
disposables.forEach {
it.dispose()
}
disposables.clear()
}
private fun refresh4SSection(state: RxSession.SecretsSynchronisationInfo) {
secureBackupCategory?.isVisible = false
// it's a lot of if / else if / else
// But it's not yet clear how to manage all cases
if (!state.isCrossSigningEnabled) {
// There is not cross signing, so we can remove the section
} else {
secureBackupCategory?.isVisible = true
if (!state.isBackupSetup) {
if (state.isCrossSigningEnabled && state.allPrivateKeysKnown) {
// You can setup recovery!
secureBackupCategory?.isVisible = true
secureBackupPreference?.isVisible = true
secureBackupPreference?.title = getString(R.string.settings_secure_backup_setup)
secureBackupPreference?.isEnabled = true
secureBackupPreference?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
BootstrapBottomSheet.show(parentFragmentManager, initCrossSigningOnly = false, forceReset4S = false)
true
}
} else {
// just hide all, you can't setup from here
// you should synchronize to get gossips
secureBackupCategory?.isVisible = false
}
return
}
// so here we know that 4S is setup
if (state.isCrossSigningTrusted && state.allPrivateKeysKnown) {
// Looks like we have all cross signing secrets and session is trusted
// Let's see if there is a megolm backup
if (!state.megolmBackupAvailable || state.megolmSecretKnown) {
// Only option here is to create a new backup if you want?
// aka reset
secureBackupCategory?.isVisible = true
secureBackupPreference?.isVisible = true
secureBackupPreference?.title = getString(R.string.settings_secure_backup_reset)
secureBackupPreference?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
BootstrapBottomSheet.show(parentFragmentManager, initCrossSigningOnly = false, forceReset4S = true)
true
}
} else if (!state.megolmSecretKnown) {
// megolm backup is available but we don't have key
// you could try to synchronize to get missing megolm key ?
secureBackupCategory?.isVisible = true
secureBackupPreference?.isVisible = true
secureBackupPreference?.title = getString(R.string.settings_secure_backup_enter_to_setup)
secureBackupPreference?.isEnabled = true
secureBackupPreference?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
(requireActivity() as? VectorBaseActivity)?.let {
it.navigator.requestSelfSessionVerification(it)
}
true
}
} else {
secureBackupCategory?.isVisible = false
}
return
} else {
// there is a backup, but this session is not trusted, or is missing some secrets
// you should enter passphrase to get them or verify against another session
secureBackupCategory?.isVisible = true
secureBackupPreference?.isVisible = true
secureBackupPreference?.title = getString(R.string.settings_secure_backup_enter_to_setup)
secureBackupPreference?.isEnabled = true
secureBackupPreference?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
(requireActivity() as? VectorBaseActivity)?.let {
it.navigator.requestSelfSessionVerification(it)
}
true
}
}
}
}
override fun bindPref() {
@ -115,26 +226,43 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
}
refreshXSigningStatus()
// secureBackupResetPreference?.let { pref ->
// val destructiveColor = ContextCompat.getColor(requireContext(), R.color.riotx_destructive_accent)
// pref.title = span {
// text = getString(R.string.keys_backup_restore_setup_recovery_key)
// textColor = destructiveColor
// }
// pref.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_delete)?.let {
// ThemeUtils.tintDrawableWithColor(it, destructiveColor)
// }
// }
}
// Todo this should be refactored and use same state as 4S section
private fun refreshXSigningStatus() {
val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
val xSigningIsEnableInAccount = crossSigningKeys != null
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
if (xSigningKeyCanSign) {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
} else if (xSigningKeysAreTrusted) {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
} else if (xSigningIsEnableInAccount) {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted)
} else {
mCrossSigningStatePreference.setIcon(android.R.color.transparent)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
when {
xSigningKeyCanSign -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
}
xSigningKeysAreTrusted -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
}
xSigningIsEnableInAccount -> {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted)
}
else -> {
mCrossSigningStatePreference.setIcon(android.R.color.transparent)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
}
}
mCrossSigningStatePreference.isVisible = true

View file

@ -121,7 +121,7 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(),
super.onActivityCreated(savedInstanceState)
setupRecoveryButton.action = {
BootstrapBottomSheet.show(parentFragmentManager, false)
BootstrapBottomSheet.show(parentFragmentManager, initCrossSigningOnly = false, forceReset4S = false)
}
exitAnywayButton.action = {

View file

@ -71,4 +71,18 @@
android:layout_height="1dp"
android:background="?attr/vctr_list_divider_color" />
<TextView
android:id="@+id/bootstrapSetupWarningTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:text="@string/reset_secure_backup_warning"
android:textColor="@color/riotx_destructive_accent"
android:drawableStart="@drawable/ic_warning_small"
android:drawablePadding="4dp"
android:textSize="14sp" />
</LinearLayout>

View file

@ -843,6 +843,15 @@
<string name="settings_send_message_with_enter">Send message with enter</string>
<string name="settings_send_message_with_enter_summary">Enter button of the soft keyboard will send message instead of adding a line break</string>
<string name="settings_secure_backup_section_title">Secure Backup</string>
<string name="settings_secure_backup_manage">Manage</string>
<string name="settings_secure_backup_setup">Set up Secure Backup</string>
<string name="settings_secure_backup_reset">Reset Secure Backup</string>
<string name="settings_secure_backup_enter_to_setup">Set up on this device</string>
<string name="settings_secure_backup_section_info">Safeguard against losing access to encrypted messages &amp; data by backing up encryption keys on your server.</string>
<string name="reset_secure_backup_title">Generate a new Security Key or set a new Security Phrase for your existing backup.</string>
<string name="reset_secure_backup_warning">This will replace your current Key or Phrase.</string>
<string name="settings_deactivate_account_section">Deactivate account</string>
<string name="settings_deactivate_my_account">Deactivate my account</string>
<string name="settings_discovery_category">Discovery</string>

View file

@ -48,6 +48,23 @@
</im.vector.riotx.core.preference.VectorPreferenceCategory>
<im.vector.riotx.core.preference.VectorPreferenceCategory
android:key="SETTINGS_CRYPTOGRAPHY_MANAGE_4S_CATEGORY_KEY"
android:title="@string/settings_secure_backup_section_title">
<im.vector.riotx.core.preference.VectorPreference
android:key="SETTINGS_SECURE_BACKUP_RECOVERY_PREFERENCE_KEY"
android:persistent="false"
android:icon="@drawable/ic_secure_backup"
android:title="@string/settings_secure_backup_setup" />
<im.vector.riotx.core.preference.VectorPreference
android:focusable="false"
android:persistent="false"
android:summary="@string/settings_secure_backup_section_info" />
</im.vector.riotx.core.preference.VectorPreferenceCategory>
<im.vector.riotx.core.preference.VectorPreferenceCategory
android:key="SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY"
android:title="@string/settings_cryptography_manage_keys">