offer a more convienient way to disable omemo from trust keys dialog

This commit is contained in:
Daniel Gultsch 2018-03-15 13:37:08 +01:00
parent 1bfc0b8da6
commit 64f040b631
5 changed files with 177 additions and 124 deletions

View file

@ -77,6 +77,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private final Map<Jid, Set<Integer>> deviceIds;
private final Map<String, XmppAxolotlMessage> messageCache;
private final FetchStatusMap fetchStatusMap;
private final Map<Jid, Boolean> fetchDeviceListStatus = new HashMap<>();
private final HashMap<Jid, List<OnDeviceIdsFetched>> fetchDeviceIdsMap = new HashMap<>();
private final SerialSingleThreadExecutor executor;
private int numPublishTriesOnEmptyPep = 0;
@ -97,6 +98,20 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
}
private boolean hasErrorFetchingDeviceList(Jid jid) {
Boolean status = fetchDeviceListStatus.get(jid);
return status != null && !status;
}
public boolean hasErrorFetchingDeviceList(List<Jid> jids) {
for(Jid jid : jids) {
if (hasErrorFetchingDeviceList(jid)) {
return true;
}
}
return false;
}
public boolean fetchMapHasErrors(List<Jid> jids) {
for (Jid jid : jids) {
if (deviceIds.get(jid) != null) {
@ -361,6 +376,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public void clearErrorsInFetchStatusMap(Jid jid) {
fetchStatusMap.clearErrorFor(jid);
fetchDeviceListStatus.remove(jid);
}
public void regenerateKeys(boolean wipeOther) {
@ -368,6 +384,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
sessions.clear();
fetchStatusMap.clear();
fetchDeviceIdsMap.clear();
fetchDeviceListStatus.clear();
publishBundlesIfNeeded(true, wipeOther);
}
@ -960,26 +977,29 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
this.fetchDeviceIdsMap.put(jid, callbacks);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching device ids for " + jid);
IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(jid);
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
synchronized (fetchDeviceIdsMap) {
List<OnDeviceIdsFetched> callbacks = fetchDeviceIdsMap.remove(jid);
if (packet.getType() == IqPacket.TYPE.RESULT) {
Element item = mXmppConnectionService.getIqParser().getItem(packet);
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
registerDevices(jid, deviceIds);
if (callbacks != null) {
for (OnDeviceIdsFetched callback : callbacks) {
callback.fetched(jid, deviceIds);
}
mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
synchronized (fetchDeviceIdsMap) {
List<OnDeviceIdsFetched> callbacks1 = fetchDeviceIdsMap.remove(jid);
if (response.getType() == IqPacket.TYPE.RESULT) {
fetchDeviceListStatus.put(jid, true);
Element item = mXmppConnectionService.getIqParser().getItem(response);
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
registerDevices(jid, deviceIds);
if (callbacks1 != null) {
for (OnDeviceIdsFetched callback1 : callbacks1) {
callback1.fetched(jid, deviceIds);
}
}
} else {
if (response.getType() == IqPacket.TYPE.TIMEOUT) {
fetchDeviceListStatus.remove(jid);
} else {
Log.d(Config.LOGTAG, packet.toString());
if (callbacks != null) {
for (OnDeviceIdsFetched callback : callbacks) {
callback.fetched(jid, null);
}
fetchDeviceListStatus.put(jid, false);
}
Log.d(Config.LOGTAG, response.toString());
if (callbacks1 != null) {
for (OnDeviceIdsFetched callback1 : callbacks1) {
callback1.fetched(jid, null);
}
}
}
@ -993,14 +1013,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
final ArrayList<Jid> unfinishedJids = new ArrayList<>(jids);
synchronized (unfinishedJids) {
for (Jid jid : unfinishedJids) {
fetchDeviceIds(jid, new OnDeviceIdsFetched() {
@Override
public void fetched(Jid jid, Set<Integer> deviceIds) {
synchronized (unfinishedJids) {
unfinishedJids.remove(jid);
if (unfinishedJids.size() == 0 && callback != null) {
callback.fetched();
}
fetchDeviceIds(jid, (j, deviceIds) -> {
synchronized (unfinishedJids) {
unfinishedJids.remove(j);
if (unfinishedJids.size() == 0 && callback != null) {
callback.fetched();
}
}
});

View file

@ -653,11 +653,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
break;
case Message.ENCRYPTION_AXOLOTL:
if (!trustKeysIfNeeded(REQUEST_TRUST_KEYS_TEXT)) {
sendAxolotlMessage(message);
sendMessage(message);
}
break;
default:
sendPlainTextMessage(message);
sendMessage(message);
}
}
@ -724,7 +724,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
case REQUEST_TRUST_KEYS_TEXT:
final String body = this.binding.textinput.getText().toString();
Message message = new Message(conversation, body, conversation.getNextEncryption());
sendAxolotlMessage(message);
sendMessage(message);
break;
case REQUEST_TRUST_KEYS_MENU:
int choice = data.getIntExtra("choice", ATTACHMENT_CHOICE_INVALID);
@ -2222,7 +2222,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
this.binding.snackbar.setVisibility(View.GONE);
}
protected void sendPlainTextMessage(Message message) {
protected void sendMessage(Message message) {
activity.xmppConnectionService.sendMessage(message);
messageSent();
}
@ -2339,11 +2339,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
builder.create().show();
}
protected void sendAxolotlMessage(final Message message) {
activity.xmppConnectionService.sendMessage(message);
messageSent();
}
public void appendText(String text) {
if (text == null) {
return;

View file

@ -1,5 +1,6 @@
package eu.siacs.conversations.ui;
import android.app.AlertDialog;
import android.content.Intent;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
@ -10,7 +11,6 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.CompoundButton;
import android.widget.Toast;
import org.whispersystems.libsignal.IdentityKey;
@ -29,7 +29,9 @@ import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
import eu.siacs.conversations.databinding.ActivityTrustKeysBinding;
import eu.siacs.conversations.databinding.KeysCardBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.IrregularUnicodeDetector;
import eu.siacs.conversations.utils.XmppUri;
@ -38,33 +40,21 @@ import rocks.xmpp.addr.Jid;
public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdated {
private final Map<String, Boolean> ownKeysToTrust = new HashMap<>();
private final Map<Jid, Map<String, Boolean>> foreignKeysToTrust = new HashMap<>();
private final OnClickListener mCancelButtonListener = v -> {
setResult(RESULT_CANCELED);
finish();
};
private List<Jid> contactJids;
private Account mAccount;
private Conversation mConversation;
private final OnClickListener mSaveButtonListener = v -> {
commitTrusts();
finishOk(false);
};
private AtomicBoolean mUseCameraHintShown = new AtomicBoolean(false);
private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS;
private final Map<String, Boolean> ownKeysToTrust = new HashMap<>();
private final Map<Jid,Map<String, Boolean>> foreignKeysToTrust = new HashMap<>();
private final OnClickListener mSaveButtonListener = new OnClickListener() {
@Override
public void onClick(View v) {
commitTrusts();
finishOk();
}
};
private final OnClickListener mCancelButtonListener = new OnClickListener() {
@Override
public void onClick(View v) {
setResult(RESULT_CANCELED);
finish();
}
};
private Toast mUseCameraHintToast = null;
private ActivityTrustKeysBinding binding;
@ -77,9 +67,9 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.binding = DataBindingUtil.setContentView(this,R.layout.activity_trust_keys);
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_trust_keys);
this.contactJids = new ArrayList<>();
for(String jid : getIntent().getStringArrayExtra("contacts")) {
for (String jid : getIntent().getStringArrayExtra("contacts")) {
try {
this.contactJids.add(Jid.of(jid));
} catch (IllegalArgumentException e) {
@ -97,7 +87,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
}
if (savedInstanceState != null) {
mUseCameraHintShown.set(savedInstanceState.getBoolean("camera_hint_shown",false));
mUseCameraHintShown.set(savedInstanceState.getBoolean("camera_hint_shown", false));
}
}
@ -116,9 +106,9 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
}
private void showCameraToast() {
mUseCameraHintToast = Toast.makeText(this,R.string.use_camera_icon_to_scan_barcode,Toast.LENGTH_LONG);
mUseCameraHintToast = Toast.makeText(this, R.string.use_camera_icon_to_scan_barcode, Toast.LENGTH_LONG);
ActionBar actionBar = getSupportActionBar();
mUseCameraHintToast.setGravity(Gravity.TOP | Gravity.END, 0 ,actionBar == null ? 0 : actionBar.getHeight());
mUseCameraHintToast.setGravity(Gravity.TOP | Gravity.END, 0, actionBar == null ? 0 : actionBar.getHeight());
mUseCameraHintToast.show();
}
@ -151,19 +141,19 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
&& mAccount != null
&& uri.hasFingerprints()
&& mAccount.getAxolotlService().getCryptoTargets(mConversation).contains(uri.getJid())) {
boolean performedVerification = xmppConnectionService.verifyFingerprints(mAccount.getRoster().getContact(uri.getJid()),uri.getFingerprints());
boolean performedVerification = xmppConnectionService.verifyFingerprints(mAccount.getRoster().getContact(uri.getJid()), uri.getFingerprints());
boolean keys = reloadFingerprints();
if (performedVerification && !keys && !hasNoOtherTrustedKeys() && !hasPendingKeyFetches()) {
Toast.makeText(this,R.string.all_omemo_keys_have_been_verified, Toast.LENGTH_SHORT).show();
finishOk();
Toast.makeText(this, R.string.all_omemo_keys_have_been_verified, Toast.LENGTH_SHORT).show();
finishOk(false);
return;
} else if (performedVerification) {
Toast.makeText(this,R.string.verified_fingerprints,Toast.LENGTH_SHORT).show();
Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
}
} else {
reloadFingerprints();
Log.d(Config.LOGTAG,"xmpp uri was: "+uri.getJid()+" has Fingerprints: "+Boolean.toString(uri.hasFingerprints()));
Toast.makeText(this,R.string.barcode_does_not_contain_fingerprints_for_this_conversation,Toast.LENGTH_SHORT).show();
Log.d(Config.LOGTAG, "xmpp uri was: " + uri.getJid() + " has Fingerprints: " + Boolean.toString(uri.hasFingerprints()));
Toast.makeText(this, R.string.barcode_does_not_contain_fingerprints_for_this_conversation, Toast.LENGTH_SHORT).show();
}
populateView();
}
@ -174,16 +164,13 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
binding.foreignKeys.removeAllViews();
boolean hasOwnKeys = false;
boolean hasForeignKeys = false;
for(final String fingerprint : ownKeysToTrust.keySet()) {
for (final String fingerprint : ownKeysToTrust.keySet()) {
hasOwnKeys = true;
addFingerprintRowWithListeners(binding.ownKeysDetails, mAccount, fingerprint, false,
FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)), false, false,
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
ownKeysToTrust.put(fingerprint, isChecked);
// own fingerprints have no impact on locked status.
}
(buttonView, isChecked) -> {
ownKeysToTrust.put(fingerprint, isChecked);
// own fingerprints have no impact on locked status.
}
);
}
@ -191,9 +178,9 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
synchronized (this.foreignKeysToTrust) {
for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) {
hasForeignKeys = true;
KeysCardBinding keysCardBinding = DataBindingUtil.inflate(getLayoutInflater(),R.layout.keys_card, binding.foreignKeys,false);
KeysCardBinding keysCardBinding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.keys_card, binding.foreignKeys, false);
final Jid jid = entry.getKey();
keysCardBinding.foreignKeysTitle.setText(IrregularUnicodeDetector.style(this,jid));
keysCardBinding.foreignKeysTitle.setText(IrregularUnicodeDetector.style(this, jid));
keysCardBinding.foreignKeysTitle.setOnClickListener(v -> switchToContactDetails(mAccount.getRoster().getContact(jid)));
final Map<String, Boolean> fingerprints = entry.getValue();
for (final String fingerprint : fingerprints.keySet()) {
@ -223,30 +210,37 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
}
}
if ((hasOwnKeys || foreignActuallyHasKeys()) && isCameraFeatureAvailable() && mUseCameraHintShown.compareAndSet(false,true)) {
if ((hasOwnKeys || foreignActuallyHasKeys()) && isCameraFeatureAvailable() && mUseCameraHintShown.compareAndSet(false, true)) {
showCameraToast();
}
binding.ownKeysTitle.setText(mAccount.getJid().asBareJid().toString());
binding.ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE);
binding.foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE);
if(hasPendingKeyFetches()) {
if (hasPendingKeyFetches()) {
setFetching();
lock();
} else {
if (!hasForeignKeys && hasNoOtherTrustedKeys()) {
binding.keyErrorMessageCard.setVisibility(View.VISIBLE);
if (lastFetchReport == AxolotlService.FetchStatus.ERROR
|| mAccount.getAxolotlService().fetchMapHasErrors(contactJids)) {
if (anyWithoutMutualPresenceSubscription(contactJids)) {
binding.keyErrorMessage.setText(R.string.error_no_keys_to_trust_presence);
} else {
binding.keyErrorMessage.setText(R.string.error_no_keys_to_trust_server_error);
}
boolean lastReportWasError = lastFetchReport == AxolotlService.FetchStatus.ERROR;
boolean errorFetchingBundle = mAccount.getAxolotlService().fetchMapHasErrors(contactJids);
boolean errorFetchingDeviceList = mAccount.getAxolotlService().hasErrorFetchingDeviceList(contactJids);
boolean anyWithoutMutualPresenceSubscription = anyWithoutMutualPresenceSubscription(contactJids);
if (errorFetchingDeviceList) {
binding.keyErrorMessage.setVisibility(View.VISIBLE);
binding.keyErrorMessage.setText(R.string.error_trustkey_device_list);
} else if (errorFetchingBundle || lastReportWasError) {
binding.keyErrorMessage.setVisibility(View.VISIBLE);
binding.keyErrorMessage.setText(R.string.error_trustkey_bundle);
} else {
binding.keyErrorMessage.setText(R.string.error_no_keys_to_trust);
binding.keyErrorMessage.setVisibility(View.GONE);
}
this.binding.keyErrorHintMutual.setVisibility(anyWithoutMutualPresenceSubscription ? View.VISIBLE : View.GONE);
Contact contact = mAccount.getRoster().getContact(contactJids.get(0));
binding.keyErrorGeneral.setText(getString(R.string.error_trustkey_general, contact.getDisplayName()));
binding.ownKeysDetails.removeAllViews();
binding.disableButton.setOnClickListener(this::disableEncryptionDialog);
binding.ownKeysCard.setVisibility(View.GONE);
binding.foreignKeys.removeAllViews();
binding.foreignKeys.setVisibility(View.GONE);
@ -256,8 +250,21 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
}
}
private boolean anyWithoutMutualPresenceSubscription(List<Jid> contactJids){
for(Jid jid : contactJids) {
private void disableEncryptionDialog(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.disable_encryption);
builder.setMessage(R.string.disable_encryption_message);
builder.setPositiveButton(R.string.disable_now, (dialog, which) -> {
mConversation.setNextEncryption(Message.ENCRYPTION_NONE);
xmppConnectionService.updateConversation(mConversation);
finishOk(true);
});
builder.setNegativeButton(R.string.cancel, null);
builder.create().show();
}
private boolean anyWithoutMutualPresenceSubscription(List<Jid> contactJids) {
for (Jid jid : contactJids) {
if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) {
return true;
}
@ -277,13 +284,13 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
}
private boolean reloadFingerprints() {
List<Jid> acceptedTargets = mConversation == null ? new ArrayList<Jid>() : mConversation.getAcceptedCryptoTargets();
List<Jid> acceptedTargets = mConversation == null ? new ArrayList<>() : mConversation.getAcceptedCryptoTargets();
ownKeysToTrust.clear();
AxolotlService service = this.mAccount.getAxolotlService();
Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided());
for(final IdentityKey identityKey : ownKeysSet) {
for (final IdentityKey identityKey : ownKeysSet) {
final String fingerprint = CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize());
if(!ownKeysToTrust.containsKey(fingerprint)) {
if (!ownKeysToTrust.containsKey(fingerprint)) {
ownKeysToTrust.put(fingerprint, false);
}
}
@ -344,25 +351,22 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
final boolean keysToTrust = reloadFingerprints();
if (report != null) {
lastFetchReport = report;
runOnUiThread(new Runnable() {
@Override
public void run() {
if (mUseCameraHintToast != null && !keysToTrust) {
mUseCameraHintToast.cancel();
}
switch (report) {
case ERROR:
Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show();
break;
case SUCCESS_TRUSTED:
Toast.makeText(TrustKeysActivity.this,R.string.blindly_trusted_omemo_keys,Toast.LENGTH_LONG).show();
break;
case SUCCESS_VERIFIED:
Toast.makeText(TrustKeysActivity.this,
Config.X509_VERIFICATION ? R.string.verified_omemo_key_with_certificate : R.string.all_omemo_keys_have_been_verified,
Toast.LENGTH_LONG).show();
break;
}
runOnUiThread(() -> {
if (mUseCameraHintToast != null && !keysToTrust) {
mUseCameraHintToast.cancel();
}
switch (report) {
case ERROR:
Toast.makeText(TrustKeysActivity.this, R.string.error_fetching_omemo_key, Toast.LENGTH_SHORT).show();
break;
case SUCCESS_TRUSTED:
Toast.makeText(TrustKeysActivity.this, R.string.blindly_trusted_omemo_keys, Toast.LENGTH_LONG).show();
break;
case SUCCESS_VERIFIED:
Toast.makeText(TrustKeysActivity.this,
Config.X509_VERIFICATION ? R.string.verified_omemo_key_with_certificate : R.string.all_omemo_keys_have_been_verified,
Toast.LENGTH_LONG).show();
break;
}
});
@ -370,25 +374,21 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) {
refreshUi();
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
finishOk();
}
});
runOnUiThread(() -> finishOk(false));
}
}
private void finishOk() {
private void finishOk(boolean disabled) {
Intent data = new Intent();
data.putExtra("choice", getIntent().getIntExtra("choice", ConversationFragment.ATTACHMENT_CHOICE_INVALID));
data.putExtra("disabled", disabled);
setResult(RESULT_OK, data);
finish();
}
private void commitTrusts() {
for(final String fingerprint :ownKeysToTrust.keySet()) {
for (final String fingerprint : ownKeysToTrust.keySet()) {
mAccount.getAxolotlService().setFingerprintTrust(
fingerprint,
FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)));

View file

@ -27,6 +27,10 @@
android:layout_marginTop="@dimen/activity_vertical_margin"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -38,14 +42,44 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/error_trustkeys_title"
android:textAppearance="@style/TextAppearance.AppCompat.Title"/>
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:layout_marginBottom="8sp"/>
<TextView
android:id="@+id/key_error_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"/>
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
<TextView
android:id="@+id/key_error_general"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Conversations.Body1"/>
<TextView
android:layout_marginTop="8sp"
android:id="@+id/key_error_hint_mutual"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Conversations.Body1.Secondary"
android:text="@string/error_trustkey_hint_mutual"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:gravity="right">
<Button
android:id="@+id/disable_button"
style="?android:attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="0dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:text="@string/disable_encryption"
android:textColor="@color/accent"/>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>

View file

@ -363,7 +363,7 @@
<string name="error_no_keys_to_trust_server_error">There are no usable keys available for this contact.\nFetching new keys from the server has been unsuccessful. Maybe there is something wrong with your contacts server.</string>
<string name="error_no_keys_to_trust_presence">There are no usable keys available for this contact.\nMake sure you have mutual presence subscription.</string>
<string name="error_no_keys_to_trust">There are no usable keys available for this contact. If you have purged any of their keys, they need to generate new ones.</string>
<string name="error_trustkeys_title">Error</string>
<string name="error_trustkeys_title">Something went wrong</string>
<string name="fetching_history_from_server">Fetching history from server</string>
<string name="no_more_history_on_server">No more history on server</string>
<string name="updating">Updating…</string>
@ -727,4 +727,11 @@
<string name="pref_scroll_to_bottom_summary">Scroll down after sending a message</string>
<string name="edit_status_message_title">Edit Status Message</string>
<string name="edit_status_message">Edit status message</string>
<string name="disable_encryption">Disable encryption</string>
<string name="error_trustkey_general">Conversations is unable to send encrypted messages to %1$s. This may be due to your contact using an outdated server or client that can not handle OMEMO.</string>
<string name="error_trustkey_device_list">Unable to fetch device list</string>
<string name="error_trustkey_bundle">Unable to fetch device bundles</string>
<string name="error_trustkey_hint_mutual">Hint: In some cases this can be fixed by adding each other your contact lists.</string>
<string name="disable_encryption_message">Are you sure you want to disable OMEMO encryption for this conversation?\nThis will allow your server administrator to read your messages, but it might be the only way to communicate with people using outdated clients.</string>
<string name="disable_now">Disable now</string>
</resources>