refactor omemo fingerprint UI code

This commit is contained in:
Christian Schneppe 2016-11-17 21:30:31 +01:00
parent 2c13740e36
commit 35fca6af27
16 changed files with 482 additions and 340 deletions

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_verified_fingerprint.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1916"
inkscape:window-height="1156"
id="namedview6"
showgrid="false"
inkscape:zoom="4.9166667"
inkscape:cx="-3.3559322"
inkscape:cy="24"
inkscape:window-x="0"
inkscape:window-y="20"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M24 2L6 10v12c0 11.11 7.67 21.47 18 24 10.33-2.53 18-12.89 18-24V10L24 2zm-4 32l-8-8 2.83-2.83L20 28.34l13.17-13.17L36 18 20 34z"
id="path4"
style="fill:#259b24;fill-opacity:0.627451" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -41,6 +41,7 @@ images = {
'ic_send_picture_offline.svg' => ['ic_send_picture_offline', 36],
'ic_send_picture_away.svg' => ['ic_send_picture_away', 36],
'ic_send_picture_dnd.svg' => ['ic_send_picture_dnd', 36],
'ic_verified_fingerprint.svg' => ['ic_verified_fingerprint', 36],
'md_switch_thumb_disable.svg' => ['switch_thumb_disable', 48],
'md_switch_thumb_off_normal.svg' => ['switch_thumb_off_normal', 48],
'md_switch_thumb_off_pressed.svg' => ['switch_thumb_off_pressed', 48],

View file

@ -0,0 +1,237 @@
package de.pixart.messenger;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.security.cert.X509Certificate;
import de.pixart.messenger.crypto.axolotl.FingerprintStatus;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.ui.XmppActivity;
import de.pixart.messenger.ui.widget.Switch;
import de.pixart.messenger.utils.CryptoHelper;
public abstract class OmemoActivity extends XmppActivity {
private Account mSelectedAccount;
private String mSelectedFingerprint;
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu,v,menuInfo);
Object account = v.getTag(R.id.TAG_ACCOUNT);
Object fingerprint = v.getTag(R.id.TAG_FINGERPRINT);
if (account != null && fingerprint != null && account instanceof Account && fingerprint instanceof String) {
getMenuInflater().inflate(R.menu.omemo_key_context, menu);
this.mSelectedAccount = (Account) account;
this.mSelectedFingerprint = (String) fingerprint;
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.purge_omemo_key:
showPurgeKeyDialog(mSelectedAccount,mSelectedFingerprint);
break;
case R.id.copy_omemo_key:
copyOmemoFingerprint(mSelectedFingerprint);
break;
}
return true;
}
protected void copyOmemoFingerprint(String fingerprint) {
if (copyTextToClipboard(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)), R.string.omemo_fingerprint)) {
Toast.makeText(
this,
R.string.toast_message_omemo_fingerprint,
Toast.LENGTH_SHORT).show();
}
}
protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight) {
final FingerprintStatus status = account.getAxolotlService().getFingerprintTrust(fingerprint);
return status != null && addFingerprintRowWithListeners(keys, account, fingerprint, highlight, status, true, true, new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
account.getAxolotlService().setFingerprintTrust(fingerprint, FingerprintStatus.createActive(isChecked));
}
});
}
protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
final String fingerprint,
boolean highlight,
FingerprintStatus status,
boolean showTag,
boolean undecidedNeedEnablement,
CompoundButton.OnCheckedChangeListener
onCheckedChangeListener) {
if (status.isCompromised()) {
return false;
}
View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
TextView key = (TextView) view.findViewById(R.id.key);
TextView keyType = (TextView) view.findViewById(R.id.key_type);
if (Config.X509_VERIFICATION && status.getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
showX509Certificate(account,fingerprint);
}
};
key.setOnClickListener(listener);
keyType.setOnClickListener(listener);
}
Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust);
ImageView verifiedFingerprintSymbol = (ImageView) view.findViewById(R.id.verified_fingerprint);
trustToggle.setVisibility(View.VISIBLE);
registerForContextMenu(view);
view.setTag(R.id.TAG_ACCOUNT,account);
view.setTag(R.id.TAG_FINGERPRINT,fingerprint);
boolean x509 = Config.X509_VERIFICATION && status.getTrust() == FingerprintStatus.Trust.VERIFIED_X509;
final View.OnClickListener toast;
trustToggle.setChecked(status.isTrusted(), false);
if (status.isActive()){
key.setTextColor(getPrimaryTextColor());
keyType.setTextColor(getSecondaryTextColor());
if (status.isVerified()) {
verifiedFingerprintSymbol.setVisibility(View.VISIBLE);
trustToggle.setVisibility(View.GONE);
verifiedFingerprintSymbol.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
replaceToast(getString(R.string.this_device_has_been_verified), false);
}
});
toast = null;
} else {
verifiedFingerprintSymbol.setVisibility(View.GONE);
trustToggle.setVisibility(View.VISIBLE);
trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);
if (status.getTrust() == FingerprintStatus.Trust.UNDECIDED && undecidedNeedEnablement) {
trustToggle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
account.getAxolotlService().setFingerprintTrust(fingerprint,FingerprintStatus.createActive(false));
v.setEnabled(true);
v.setOnClickListener(null);
}
});
trustToggle.setEnabled(false);
} else {
trustToggle.setOnClickListener(null);
trustToggle.setEnabled(true);
}
toast = new View.OnClickListener() {
@Override
public void onClick(View v) {
hideToast();
}
};
}
} else {
key.setTextColor(getTertiaryTextColor());
keyType.setTextColor(getTertiaryTextColor());
toast = new View.OnClickListener() {
@Override
public void onClick(View v) {
replaceToast(getString(R.string.this_device_is_no_longer_in_use), false);
}
};
if (status.isVerified()) {
trustToggle.setVisibility(View.GONE);
verifiedFingerprintSymbol.setVisibility(View.VISIBLE);
verifiedFingerprintSymbol.setOnClickListener(toast);
} else {
trustToggle.setVisibility(View.VISIBLE);
verifiedFingerprintSymbol.setVisibility(View.GONE);
trustToggle.setOnClickListener(null);
trustToggle.setEnabled(false);
trustToggle.setOnClickListener(toast);
}
}
view.setOnClickListener(toast);
key.setOnClickListener(toast);
keyType.setOnClickListener(toast);
if (showTag) {
keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
} else {
keyType.setVisibility(View.GONE);
}
if (highlight) {
keyType.setTextColor(getResources().getColor(R.color.accent));
keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message));
} else {
keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
}
key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
keys.addView(view);
return true;
}
public void showPurgeKeyDialog(final Account account, final String fingerprint) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.purge_key));
builder.setIconAttribute(android.R.attr.alertDialogIcon);
builder.setMessage(getString(R.string.purge_key_desc_part1)
+ "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2))
+ "\n\n" + getString(R.string.purge_key_desc_part2));
builder.setNegativeButton(getString(R.string.cancel), null);
builder.setPositiveButton(getString(R.string.purge_key),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
account.getAxolotlService().purgeKey(fingerprint);
refreshUi();
}
});
builder.create().show();
}
private void showX509Certificate(Account account, String fingerprint) {
X509Certificate x509Certificate = account.getAxolotlService().getFingerprintCertificate(fingerprint);
if (x509Certificate != null) {
showCertificateInformationDialog(CryptoHelper.extractCertificateInformation(x509Certificate));
} else {
Toast.makeText(this,R.string.certificate_not_found, Toast.LENGTH_SHORT).show();
}
}
private void showCertificateInformationDialog(Bundle bundle) {
View view = getLayoutInflater().inflate(R.layout.certificate_information, null);
final String not_available = getString(R.string.certicate_info_not_available);
TextView subject_cn = (TextView) view.findViewById(R.id.subject_cn);
TextView subject_o = (TextView) view.findViewById(R.id.subject_o);
TextView issuer_cn = (TextView) view.findViewById(R.id.issuer_cn);
TextView issuer_o = (TextView) view.findViewById(R.id.issuer_o);
TextView sha1 = (TextView) view.findViewById(R.id.sha1);
subject_cn.setText(bundle.getString("subject_cn", not_available));
subject_o.setText(bundle.getString("subject_o", not_available));
issuer_cn.setText(bundle.getString("issuer_cn", not_available));
issuer_o.setText(bundle.getString("issuer_o", not_available));
sha1.setText(bundle.getString("sha1", not_available));
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.certificate_information);
builder.setView(view);
builder.setPositiveButton(R.string.ok, null);
builder.create().show();
}
}

View file

@ -27,20 +27,18 @@ import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.QuickContactBadge;
import android.widget.TextView;
import android.widget.Toast;
import com.wefika.flowlayout.FlowLayout;
import org.openintents.openpgp.util.OpenPgpUtils;
import java.security.cert.X509Certificate;
import java.util.List;
import de.pixart.messenger.Config;
import de.pixart.messenger.OmemoActivity;
import de.pixart.messenger.R;
import de.pixart.messenger.crypto.PgpEngine;
import de.pixart.messenger.crypto.axolotl.AxolotlService;
import de.pixart.messenger.crypto.axolotl.FingerprintStatus;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Contact;
import de.pixart.messenger.entities.Conversation;
@ -55,7 +53,7 @@ import de.pixart.messenger.xmpp.XmppConnection;
import de.pixart.messenger.xmpp.jid.InvalidJidException;
import de.pixart.messenger.xmpp.jid.Jid;
public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated {
public class ContactDetailsActivity extends OmemoActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated {
public static final String ACTION_VIEW_CONTACT = "view_contact";
private Conversation mConversation;
@ -462,12 +460,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
if (Config.supportOmemo()) {
for (final String fingerprint : contact.getAccount().getAxolotlService().getFingerprintsForContact(contact)) {
boolean highlight = fingerprint.equals(messageFingerprint);
hasKeys |= addFingerprintRow(keys, contact.getAccount(), fingerprint, highlight, new OnClickListener() {
@Override
public void onClick(View v) {
onOmemoKeyClicked(contact.getAccount(), fingerprint);
}
});
hasKeys |= addFingerprintRow(keys, contact.getAccount(), fingerprint, highlight);
}
}
if (Config.supportOpenPgp() && contact.getPgpKeyId() != 0) {
@ -523,40 +516,6 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
}
}
private void onOmemoKeyClicked(Account account, String fingerprint) {
FingerprintStatus status = account.getAxolotlService().getFingerprintTrust(fingerprint);
if (Config.X509_VERIFICATION && status != null && status.getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
X509Certificate x509Certificate = account.getAxolotlService().getFingerprintCertificate(fingerprint);
if (x509Certificate != null) {
showCertificateInformationDialog(CryptoHelper.extractCertificateInformation(x509Certificate));
} else {
Toast.makeText(this,R.string.certificate_not_found, Toast.LENGTH_SHORT).show();
}
}
}
private void showCertificateInformationDialog(Bundle bundle) {
View view = getLayoutInflater().inflate(R.layout.certificate_information, null);
final String not_available = getString(R.string.certicate_info_not_available);
TextView subject_cn = (TextView) view.findViewById(R.id.subject_cn);
TextView subject_o = (TextView) view.findViewById(R.id.subject_o);
TextView issuer_cn = (TextView) view.findViewById(R.id.issuer_cn);
TextView issuer_o = (TextView) view.findViewById(R.id.issuer_o);
TextView sha1 = (TextView) view.findViewById(R.id.sha1);
subject_cn.setText(bundle.getString("subject_cn", not_available));
subject_o.setText(bundle.getString("subject_o", not_available));
issuer_cn.setText(bundle.getString("issuer_cn", not_available));
issuer_o.setText(bundle.getString("issuer_o", not_available));
sha1.setText(bundle.getString("sha1", not_available));
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.certificate_information);
builder.setView(view);
builder.setPositiveButton(R.string.ok, null);
builder.create().show();
}
protected void confirmToDeleteFingerprint(final String fingerprint) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.delete_fingerprint);
@ -577,7 +536,6 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
builder.create().show();
}
@Override
public void onBackendConnected() {
if ((accountJid != null) && (contactJid != null)) {
Account account = xmppConnectionService

View file

@ -41,6 +41,7 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import de.pixart.messenger.Config;
import de.pixart.messenger.OmemoActivity;
import de.pixart.messenger.R;
import de.pixart.messenger.crypto.axolotl.AxolotlService;
import de.pixart.messenger.entities.Account;
@ -59,19 +60,19 @@ import de.pixart.messenger.xmpp.jid.InvalidJidException;
import de.pixart.messenger.xmpp.jid.Jid;
import de.pixart.messenger.xmpp.pep.Avatar;
public class EditAccountActivity extends XmppActivity implements OnAccountUpdate,
public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate,
OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched {
private static final int REQUEST_DATA_SAVER = 0x37af244;
private AutoCompleteTextView mAccountJid;
private static final int REQUEST_DATA_SAVER = 0x37af244;
private AutoCompleteTextView mAccountJid;
private EditText mPassword;
private EditText mPasswordConfirm;
private CheckBox mRegisterNew;
private Button mCancelButton;
private Button mSaveButton;
private Button mDisableOsOptimizationsButton;
private TextView mDisableOsOptimizationsHeadline;
private TextView getmDisableOsOptimizationsBody;
private Button mDisableOsOptimizationsButton;
private TextView mDisableOsOptimizationsHeadline;
private TextView getmDisableOsOptimizationsBody;
private TableLayout mMoreTable;
private LinearLayout mStats;
@ -190,9 +191,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
return;
}
if (registerNewAccount) {
if (XmppConnection.errorMessage != null) {
Toast.makeText(EditAccountActivity.this,XmppConnection.errorMessage,Toast.LENGTH_LONG).show();
}
if (XmppConnection.errorMessage != null) {
Toast.makeText(EditAccountActivity.this,XmppConnection.errorMessage,Toast.LENGTH_LONG).show();
}
if (!password.equals(passwordConfirm)) {
mPasswordConfirm.setError(getString(R.string.passwords_do_not_match));
mPasswordConfirm.requestFocus();
@ -206,11 +207,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
mAccount.setJid(jid);
mAccount.setPort(numericPort);
mAccount.setHostname(hostname);
if (XmppConnection.errorMessage != null) {
mAccountJid.setError(XmppConnection.errorMessage);
} else {
mAccountJid.setError(null);
}
if (XmppConnection.errorMessage != null) {
mAccountJid.setError(XmppConnection.errorMessage);
} else {
mAccountJid.setError(null);
}
mPasswordConfirm.setError(null);
mAccount.setPassword(password);
mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
@ -480,10 +481,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mAvatar.setOnClickListener(this.mAvatarClickListener);
this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new);
this.mStats = (LinearLayout) findViewById(R.id.stats);
this.mOsOptimizations = (RelativeLayout) findViewById(R.id.os_optimization);
this.mDisableOsOptimizationsButton = (Button) findViewById(R.id.os_optimization_disable);
this.mDisableOsOptimizationsHeadline = (TextView) findViewById(R.id.os_optimization_headline);
this.getmDisableOsOptimizationsBody = (TextView) findViewById(R.id.os_optimization_body);
this.mOsOptimizations = (RelativeLayout) findViewById(R.id.os_optimization);
this.mDisableOsOptimizationsButton = (Button) findViewById(R.id.os_optimization_disable);
this.mDisableOsOptimizationsHeadline = (TextView) findViewById(R.id.os_optimization_headline);
this.getmDisableOsOptimizationsBody = (TextView) findViewById(R.id.os_optimization_body);
this.mSessionEst = (TextView) findViewById(R.id.session_est);
this.mServerInfoRosterVersion = (TextView) findViewById(R.id.server_info_roster_version);
this.mServerInfoCarbons = (TextView) findViewById(R.id.server_info_carbons);
@ -516,9 +517,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
this.mCancelButton.setOnClickListener(this.mCancelButtonClickListener);
this.mMoreTable = (TableLayout) findViewById(R.id.server_info_more);
if (savedInstanceState != null && savedInstanceState.getBoolean("showMoreTable")) {
changeMoreTableVisibility(true);
}
if (savedInstanceState != null && savedInstanceState.getBoolean("showMoreTable")) {
changeMoreTableVisibility(true);
}
final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton buttonView,
@ -584,22 +585,22 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
if (showMoreInfo.isVisible()) {
showMoreInfo.setChecked(mMoreTable.getVisibility() == View.VISIBLE);
}
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
if (showMoreInfo.isVisible()) {
showMoreInfo.setChecked(mMoreTable.getVisibility() == View.VISIBLE);
}
return super.onPrepareOptionsMenu(menu);
}
@Override
protected void onStart() {
super.onStart();
final int theme = findTheme();
if (this.mTheme != theme) {
recreate();
} else if (getIntent() != null) {
final int theme = findTheme();
if (this.mTheme != theme) {
recreate();
} else if (getIntent() != null) {
try {
this.jidToEdit = Jid.fromString(getIntent().getStringExtra("jid"));
} catch (final InvalidJidException | NullPointerException ignored) {
@ -637,12 +638,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
if (mAccount != null) {
savedInstanceState.putString("account", mAccount.getJid().toBareJid().toString());
savedInstanceState.putBoolean("initMode", mInitMode);
savedInstanceState.putBoolean("showMoreTable", mMoreTable.getVisibility() == View.VISIBLE);
savedInstanceState.putBoolean("showMoreTable", mMoreTable.getVisibility() == View.VISIBLE);
}
super.onSaveInstanceState(savedInstanceState);
}
@Override
protected void onBackendConnected() {
boolean init = true;
if (mSavedInstanceAccount != null) {
@ -714,7 +714,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
startActivity(showBlocklistIntent);
break;
case R.id.action_server_info_show_more:
changeMoreTableVisibility(!item.isChecked());
changeMoreTableVisibility(!item.isChecked());
break;
case R.id.action_change_password_on_server:
gotoChangePassword(null);
@ -738,9 +738,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
return super.onOptionsItemSelected(item);
}
private void changeMoreTableVisibility(boolean visible) {
mMoreTable.setVisibility(visible ? View.VISIBLE : View.GONE);
}
private void changeMoreTableVisibility(boolean visible) {
mMoreTable.setVisibility(visible ? View.VISIBLE : View.GONE);
}
private void gotoChangePassword(String newPassword) {
final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class);
@ -803,9 +803,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
Features features = this.mAccount.getXmppConnection().getFeatures();
this.mStats.setVisibility(View.VISIBLE);
boolean showBatteryWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery();
boolean showDataSaverWarning = isAffectedByDataSaver();
showOsOptimizationWarning(showBatteryWarning,showDataSaverWarning);
boolean showBatteryWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery();
boolean showDataSaverWarning = isAffectedByDataSaver();
showOsOptimizationWarning(showBatteryWarning,showDataSaverWarning);
this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection()
.getLastSessionEstablished()));
if (features.rosterVersioning()) {
@ -901,13 +901,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
@Override
public void onClick(final View v) {
if (copyTextToClipboard(ownAxolotlFingerprint.substring(2), R.string.omemo_fingerprint)) {
Toast.makeText(
EditAccountActivity.this,
R.string.toast_message_omemo_fingerprint,
Toast.LENGTH_SHORT).show();
}
copyOmemoFingerprint(ownAxolotlFingerprint);
}
});
if (Config.SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON) {
@ -932,7 +926,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
continue;
}
boolean highlight = fingerprint.equals(messageFingerprint);
hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight, null);
hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight);
}
if (hasKeys && Config.supportOmemo()) {
keysCard.setVisibility(View.VISIBLE);
@ -964,47 +958,47 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
}
private void showOsOptimizationWarning(boolean showBatteryWarning, boolean showDataSaverWarning) {
this.mOsOptimizations.setVisibility(showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE);
if (showDataSaverWarning) {
this.mDisableOsOptimizationsHeadline.setText(R.string.data_saver_enabled);
this.getmDisableOsOptimizationsBody.setText(R.string.data_saver_enabled_explained);
this.mDisableOsOptimizationsButton.setText(R.string.allow);
this.mDisableOsOptimizationsButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS);
Uri uri = Uri.parse("package:" + getPackageName());
intent.setData(uri);
try {
startActivityForResult(intent, REQUEST_DATA_SAVER);
} catch (ActivityNotFoundException e) {
Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_data_saver, Toast.LENGTH_SHORT).show();
}
}
});
} else if (showBatteryWarning) {
this.mDisableOsOptimizationsButton.setText(R.string.disable);
this.mDisableOsOptimizationsHeadline.setText(R.string.battery_optimizations_enabled);
this.getmDisableOsOptimizationsBody.setText(R.string.battery_optimizations_enabled_explained);
this.mDisableOsOptimizationsButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
Uri uri = Uri.parse("package:" + getPackageName());
intent.setData(uri);
try {
startActivityForResult(intent, REQUEST_BATTERY_OP);
} catch (ActivityNotFoundException e) {
Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
}
}
});
}
}
private void showOsOptimizationWarning(boolean showBatteryWarning, boolean showDataSaverWarning) {
this.mOsOptimizations.setVisibility(showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE);
if (showDataSaverWarning) {
this.mDisableOsOptimizationsHeadline.setText(R.string.data_saver_enabled);
this.getmDisableOsOptimizationsBody.setText(R.string.data_saver_enabled_explained);
this.mDisableOsOptimizationsButton.setText(R.string.allow);
this.mDisableOsOptimizationsButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS);
Uri uri = Uri.parse("package:" + getPackageName());
intent.setData(uri);
try {
startActivityForResult(intent, REQUEST_DATA_SAVER);
} catch (ActivityNotFoundException e) {
Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_data_saver, Toast.LENGTH_SHORT).show();
}
}
});
} else if (showBatteryWarning) {
this.mDisableOsOptimizationsButton.setText(R.string.disable);
this.mDisableOsOptimizationsHeadline.setText(R.string.battery_optimizations_enabled);
this.getmDisableOsOptimizationsBody.setText(R.string.battery_optimizations_enabled_explained);
this.mDisableOsOptimizationsButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
Uri uri = Uri.parse("package:" + getPackageName());
intent.setData(uri);
try {
startActivityForResult(intent, REQUEST_BATTERY_OP);
} catch (ActivityNotFoundException e) {
Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
}
}
});
}
}
public void showRegenerateAxolotlKeyDialog() {
public void showRegenerateAxolotlKeyDialog() {
Builder builder = new Builder(this);
builder.setTitle("Regenerate Key");
builder.setIconAttribute(android.R.attr.alertDialogIcon);

View file

@ -18,6 +18,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import de.pixart.messenger.OmemoActivity;
import de.pixart.messenger.R;
import de.pixart.messenger.crypto.axolotl.AxolotlService;
import de.pixart.messenger.crypto.axolotl.FingerprintStatus;
@ -27,7 +28,7 @@ import de.pixart.messenger.xmpp.OnKeyStatusUpdated;
import de.pixart.messenger.xmpp.jid.InvalidJidException;
import de.pixart.messenger.xmpp.jid.Jid;
public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated {
public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdated {
private List<Jid> contactJids;
private Account mAccount;
@ -108,16 +109,14 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
for(final String fingerprint : ownKeysToTrust.keySet()) {
hasOwnKeys = true;
addFingerprintRowWithListeners(ownKeys, mAccount, fingerprint, false,
FingerprintStatus.createActive(ownKeysToTrust.get(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.
}
},
null,
null
}
);
}
@ -133,16 +132,14 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
final Map<String, Boolean> fingerprints = entry.getValue();
for (final String fingerprint : fingerprints.keySet()) {
addFingerprintRowWithListeners(keysContainer, mAccount, fingerprint, false,
FingerprintStatus.createActive(fingerprints.get(fingerprint)), false,
FingerprintStatus.createActive(fingerprints.get(fingerprint)), false, false,
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
fingerprints.put(fingerprint, isChecked);
lockOrUnlockAsNeeded();
}
},
null,
null
}
);
}
if (fingerprints.size() == 0) {
@ -211,7 +208,6 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
return ownKeysSet.size() + foreignKeysToTrust.size() > 0;
}
@Override
public void onBackendConnected() {
Intent intent = getIntent();
this.mAccount = extractAccount(intent);

View file

@ -48,11 +48,8 @@ import android.util.Pair;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.google.zxing.BarcodeFormat;
@ -76,7 +73,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.crypto.axolotl.FingerprintStatus;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Contact;
import de.pixart.messenger.entities.Conversation;
@ -86,7 +82,6 @@ import de.pixart.messenger.entities.Presences;
import de.pixart.messenger.services.AvatarService;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.services.XmppConnectionService.XmppConnectionBinder;
import de.pixart.messenger.ui.widget.Switch;
import de.pixart.messenger.utils.CryptoHelper;
import de.pixart.messenger.utils.ExceptionHelper;
import de.pixart.messenger.utils.UIHelper;
@ -775,131 +770,6 @@ public abstract class XmppActivity extends Activity {
builder.create().show();
}
protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight, View.OnClickListener onKeyClickedListener) {
final FingerprintStatus status = account.getAxolotlService().getFingerprintTrust(fingerprint);
if (status == null) {
return false;
}
return addFingerprintRowWithListeners(keys, account, fingerprint, highlight, status, true,
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
account.getAxolotlService().setFingerprintTrust(fingerprint,FingerprintStatus.createActive(isChecked));
}
},
new View.OnClickListener() {
@Override
public void onClick(View v) {
account.getAxolotlService().setFingerprintTrust(fingerprint,FingerprintStatus.createActive(false));
v.setEnabled(true);
}
},
onKeyClickedListener
);
}
protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
final String fingerprint,
boolean highlight,
FingerprintStatus status,
boolean showTag,
CompoundButton.OnCheckedChangeListener
onCheckedChangeListener,
View.OnClickListener onClickListener,
View.OnClickListener onKeyClickedListener) {
if (status.isCompromised()) {
return false;
}
View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
TextView key = (TextView) view.findViewById(R.id.key);
key.setOnClickListener(onKeyClickedListener);
TextView keyType = (TextView) view.findViewById(R.id.key_type);
keyType.setOnClickListener(onKeyClickedListener);
Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust);
trustToggle.setVisibility(View.VISIBLE);
final View.OnLongClickListener purge = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
showPurgeKeyDialog(account, fingerprint);
return true;
}
};
view.setOnLongClickListener(purge);
key.setOnLongClickListener(purge);
keyType.setOnLongClickListener(purge);
boolean x509 = Config.X509_VERIFICATION && status.getTrust() == FingerprintStatus.Trust.VERIFIED_X509;
final View.OnClickListener toast;
trustToggle.setChecked(status.isTrusted(), false);
if (status.isActive()) {
key.setTextColor(getPrimaryTextColor());
keyType.setTextColor(getSecondaryTextColor());
trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);
if (status.getTrust() == FingerprintStatus.Trust.UNDECIDED) {
trustToggle.setOnClickListener(onClickListener);
trustToggle.setEnabled(false);
} else {
trustToggle.setOnClickListener(null);
trustToggle.setEnabled(true);
}
toast = new View.OnClickListener() {
@Override
public void onClick(View v) {
hideToast();
}
};
} else {
key.setTextColor(getTertiaryTextColor());
keyType.setTextColor(getTertiaryTextColor());
trustToggle.setOnClickListener(null);
trustToggle.setEnabled(false);
toast = new View.OnClickListener() {
@Override
public void onClick(View v) {
replaceToast(getString(R.string.this_device_is_no_longer_in_use), false);
}
};
trustToggle.setOnClickListener(toast);
}
view.setOnClickListener(toast);
key.setOnClickListener(toast);
keyType.setOnClickListener(toast);
if (showTag) {
keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
} else {
keyType.setVisibility(View.GONE);
}
if (highlight) {
keyType.setTextColor(getResources().getColor(R.color.accent));
keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message));
} else {
keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
}
key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
keys.addView(view);
return true;
}
public void showPurgeKeyDialog(final Account account, final String fingerprint) {
Builder builder = new Builder(this);
builder.setTitle(getString(R.string.purge_key));
builder.setIconAttribute(android.R.attr.alertDialogIcon);
builder.setMessage(getString(R.string.purge_key_desc_part1)
+ "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2))
+ "\n\n" + getString(R.string.purge_key_desc_part2));
builder.setNegativeButton(getString(R.string.cancel), null);
builder.setPositiveButton(getString(R.string.purge_key),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
account.getAxolotlService().purgeKey(fingerprint);
refreshUi();
}
});
builder.create().show();
}
public boolean hasStoragePermission(int requestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -1,16 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent" >
android:layout_height="match_parent"
android:longClickable="true">
<RelativeLayout
android:id="@+id/key_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingLeft="8dp"
android:paddingBottom="8dp">
android:paddingTop="8dp">
<TextView
android:id="@+id/key"
@ -18,10 +19,11 @@
android:layout_height="wrap_content"
android:textColor="@color/black87"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/tgl_trust"
android:layout_toLeftOf="@+id/action_container"
android:textSize="?attr/TextSizeBody"
android:typeface="monospace"
android:fontFamily="monospace"/>
android:fontFamily="monospace"
android:longClickable="true"/>
<TextView
android:id="@+id/key_type"
@ -31,7 +33,8 @@
android:layout_alignParentLeft="true"
android:layout_below="@+id/key"
android:maxLines="1"
android:textSize="?attr/TextSizeInfo"/>
android:textSize="?attr/TextSizeInfo"
android:longClickable="true"/>
<TextView
android:id="@+id/key_trust"
@ -41,29 +44,45 @@
android:layout_below="@+id/key"
android:visibility="gone"
android:textColor="@color/black54"
android:textSize="?attr/TextSizeInfo"/>
android:textSize="?attr/TextSizeInfo"
android:longClickable="true"/>
<ImageButton
android:id="@+id/button_remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_toRightOf="@+id/key"
android:layout_centerVertical="true"
android:background="?android:selectableItemBackground"
android:padding="@dimen/image_button_padding"
android:src="?attr/icon_remove"
android:visibility="gone" />
<LinearLayout
android:id="@+id/action_container"
android:layout_width="96dp"
android:layout_marginRight="-32dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_alignParentRight="true"
android:layout_centerVertical="true">
<ImageButton
android:layout_gravity="center_horizontal"
android:id="@+id/button_remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
android:padding="@dimen/image_button_padding"
android:src="?attr/icon_remove"
android:layout_marginRight="16dp"
android:visibility="gone" />
<ImageView
android:visibility="gone"
android:id="@+id/verified_fingerprint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginRight="16dp"
android:src="@drawable/ic_verified_fingerprint" />
<de.pixart.messenger.ui.widget.Switch
android:id="@+id/tgl_trust"
android:visibility="invisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
style="@style/MD"/>
<de.pixart.messenger.ui.widget.Switch
android:id="@+id/tgl_trust"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
style="@style/MD"/>
</LinearLayout>
</RelativeLayout>
</RelativeLayout>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/purge_omemo_key"
android:title="@string/purge_key"/>
<item
android:id="@+id/copy_omemo_key"
android:title="@string/copy_fingerprint"/>
</menu>

View file

@ -4,4 +4,6 @@
<item name="message_image_view" type="id" />
<item name="message_video_view" type="id" />
<item name="import_text" type="id" />
<item type="id" name="TAG_ACCOUNT"/>
<item type="id" name="TAG_FINGERPRINT"/>
</resources>

View file

@ -101,13 +101,13 @@
<string name="pref_xmpp_resource">XMPP resource</string>
<string name="pref_xmpp_resource_summary">The name this client identifies itself with</string>
<string name="pref_accept_files_wifi">Accept files in WiFi connections</string>
<string name="pref_accept_files_summary_wifi">When connected to wifi automatically accept files smaller than…</string>
<string name="pref_accept_files_mobile">Accept files in mobile connections</string>
<string name="pref_accept_files_summary_mobile">When connected with mobile data automatically accept files smaller than…</string>
<string name="pref_accept_files_mobileroaming">Accept files in mobile roaming connections</string>
<string name="pref_accept_files_summary_mobileroaming">When connected with mobile data in roaming automatically accept files smaller than…</string>
<string name="pref_accept_files_summary_wifi">When connected to wifi automatically accept files smaller than…</string>
<string name="pref_accept_files_mobile">Accept files in mobile connections</string>
<string name="pref_accept_files_summary_mobile">When connected with mobile data automatically accept files smaller than…</string>
<string name="pref_accept_files_mobileroaming">Accept files in mobile roaming connections</string>
<string name="pref_accept_files_summary_mobileroaming">When connected with mobile data in roaming automatically accept files smaller than…</string>
<string name="pref_attachments">Attachments</string>
<string name="pref_notification_settings">Notification</string>
<string name="pref_notification_settings">Notification</string>
<string name="pref_notifications">Notifications</string>
<string name="pref_notifications_summary">Notify when a new message arrives</string>
<string name="pref_vibrate">Vibrate</string>
@ -116,13 +116,13 @@
<string name="pref_led_summary">Blink notification light when a new message arrives</string>
<string name="pref_sound">Ringtone</string>
<string name="pref_sound_summary">Play sound when a new message arrives</string>
<string name="pref_send_crash">Send crash reports</string>
<string name="pref_send_crash">Send crash reports</string>
<string name="pref_send_crash_summary">By sending in stack traces you are helping the ongoing development of Pix-Art Messenger</string>
<string name="pref_confirm_messages">Confirm Messages</string>
<string name="pref_confirm_messages_summary">Let your contact know when you have received and read a message</string>
<string name="pref_ui_options">UI</string>
<string name="openpgp_error">OpenKeychain reported an error</string>
<string name="accept">Accept</string>
<string name="accept">Accept</string>
<string name="error">An error has occurred</string>
<string name="pref_grant_presence_updates">Grant presence updates</string>
<string name="pref_grant_presence_updates_summary">Preemptively grant and ask for presence subscription for contacts you created</string>
@ -197,7 +197,7 @@
<string name="last_seen_hours">%d hours ago</string>
<string name="last_seen_day">1 day ago</string>
<string name="last_seen_days">%d days ago</string>
<string name="install_openkeychain">Encrypted message. Please install OpenKeychain to decrypt.</string>
<string name="install_openkeychain">Encrypted message. Please install OpenKeychain to decrypt.</string>
<string name="unknown_otr_fingerprint">Unknown OTR fingerprint</string>
<string name="openpgp_messages_found">OpenPGP encrypted messages found</string>
<string name="your_fingerprint">Your fingerprint</string>
@ -273,7 +273,7 @@
<string name="pref_dont_save_encrypted">Dont save encrypted messages</string>
<string name="pref_dont_save_encrypted_summary">Warning: This could lead to message loss</string>
<string name="pref_expert_options">Expert settings</string>
<string name="title_activity_about">About Pix-Art Messenger</string>
<string name="title_activity_about">About Pix-Art Messenger</string>
<string name="pref_about_conversations_summary">Build and licensing information</string>
<string name="pref_about_message" translatable="false">Pix-Art Messenger
\n\nCopyright © 2014-2016 Christian Schneppe
@ -330,7 +330,7 @@
<string name="pref_use_indicate_received_summary">Received messages will be marked with a green tick if supported</string>
<string name="pref_use_send_button_to_indicate_status_summary">Colorize send button to indicate contact status</string>
<string name="pref_expert_options_other">Other</string>
<string name="pref_autojoin">Automatically join conferences</string>
<string name="pref_autojoin">Automatically join conferences</string>
<string name="pref_autojoin_summary">Respect the autojoin flag in conference bookmarks</string>
<string name="toast_message_otr_fingerprint">OTR fingerprint copied to clipboard!</string>
<string name="toast_message_omemo_fingerprint">OMEMO fingerprint copied to clipboard!</string>
@ -338,7 +338,7 @@
<string name="conference_members_only">This conference is members only</string>
<string name="conference_kicked">You have been kicked from this conference</string>
<string name="conference_shutdown">The conference was shut down</string>
<string name="using_account">using account %s</string>
<string name="using_account">using account %s</string>
<string name="checking_x">Checking %s on HTTP host</string>
<string name="not_connected_try_again">You are not connected. Try again later</string>
<string name="check_x_filesize">Check %s size</string>
@ -394,7 +394,7 @@
<string name="enable_notifications">Enable notifications</string>
<string name="no_conference_server_found">No conference server found</string>
<string name="conference_creation_failed">Conference creation failed!</string>s
<string name="account_image_description">Account avatar</string>
<string name="account_image_description">Account avatar</string>
<string name="copy_otr_clipboard_description">Copy OTR fingerprint to clipboard</string>
<string name="copy_omemo_clipboard_description">Copy OMEMO fingerprint to clipboard</string>
<string name="regenerate_omemo_key">Regenerate OMEMO key</string>
@ -471,7 +471,7 @@
<string name="sending_x_file">Sending %s</string>
<string name="offering_x_file">Offering %s</string>
<string name="hide_offline">Hide offline</string>
<string name="is_typing">is typing…</string>
<string name="is_typing">is typing…</string>
<string name="pref_chat_states">Typing notifications</string>
<string name="pref_chat_states_summary">Let your contact know when you are writing a new message</string>
<string name="send_location">Send location</string>
@ -552,7 +552,7 @@
<string name="verified_omemo_key_with_certificate">Verified OMEMO key with certificate!</string>
<string name="device_does_not_support_certificates">Your device does not support the selection of client certificates!</string>
<string name="changelog">Changes</string>
<string name="account_settings_hostname">Hostname</string>
<string name="account_settings_hostname">Hostname</string>
<string name="account_settings_port">Port</string>
<string name="hostname_or_onion">Server- or .onion-Address</string>
<string name="not_a_valid_port">This is not a valid port number</string>
@ -644,13 +644,13 @@
<string name="creating_conference">Creating conference…</string>
<string name="import_text">There is a backup on your device which can be imported.\nPossibly you will be asked to uninstall the old version or Conversations and your Messenger will be restarted during backup process. Shall the backup be imported? </string>
<string name="import_database">Import backup</string>
<string name="invite_again">Invite again</string>
<string name="invite_again">Invite again</string>
<string name="inviteUser_Subject">has invited you via</string>
<string name="InviteText">Hello,\n\nthe user %s has invited you to Pix-Art Messenger. If you are using Android, give it a try and click the following link to start over...</string>
<string name="pref_broadcast_last_activity">Broadcast Last User Interaction</string>
<string name="pref_broadcast_last_activity">Broadcast Last User Interaction</string>
<string name="pref_broadcast_last_activity_summary">Let all your contacts know when use Pix-Art Messenger</string>
<string name="invite_user">Invite to Pix-Art Messenger</string>
<string name="request_permissions_message">Pix-Art Messenger will ask you to allow a few permissions. It is important that you allow all these permissions to use all features of this messenger. If you deny any of these permissions the app will close itself.</string>
<string name="invite_user">Invite to Pix-Art Messenger</string>
<string name="request_permissions_message">Pix-Art Messenger will ask you to allow a few permissions. It is important that you allow all these permissions to use all features of this messenger. If you deny any of these permissions the app will close itself.</string>
<string name="request_permissions_message_again">You have denied some or all permissions needed for Pix-Art Messenger. Would you like to jump to the settings and allow these permissions? If you denie any of these permissions, the app will close itself.</string>
<string name="unable_to_connect_to_keychain">Unable to connect to OpenKeychain</string>
<string name="no_permission">Pix-Art Messenger has no permissions</string>
@ -678,7 +678,7 @@
<string name="preparing_video">Prepare video for transmission. Please wait…</string>
<string name="compressing_video">Compressing video, please wait…</string>
<string name="shared_video_with_x">Shared video with %s</string>
<string name="no_permission_to_access_x">No permission to access %s</string>
<string name="no_permission_to_access_x">No permission to access %s</string>
<string name="remote_server_not_found">Remote server not found</string>
<string name="error_file_corrupt">This file seems to be corrupt.</string>
<string name="unable_to_update_account">Unable to update account</string>
@ -687,23 +687,25 @@
<string name="wrong_conference_configuration">This is not a private, non-anonymous conference.</string>
<string name="this_conference_has_no_members">There are no members in this conference.</string>
<string name="report_jid_as_spammer">Report this JID as sending unwanted messages.</string>
<string name="create_issue">Report an issue</string>
<string name="pref_delete_omemo_identities">Delete OMEMO identities</string>
<string name="pref_delete_omemo_identities_summary">Regenerate your OMEMO keys. All your contacts will have to verify you again. Use this only as a last resort.</string>
<string name="delete_selected_keys">Delete selected keys</string>
<string name="missing_presence_subscription">Missing presence subscription</string>
<string name="missing_omemo_keys">Missing OMEMO keys</string>
<string name="error_publish_avatar_offline">You need to be connected to publish your avatar.</string>
<string name="select_text">Select text</string>
<string name="show_error_message">Show error message</string>
<string name="error_message">Error Message</string>
<string name="data_saver_enabled">Data saver enabled</string>
<string name="data_saver_enabled_explained">Your operating system is restricting Pix-Art Messenger from accessing the Internet when in background. To receive notifications of new messages you should allow Pix-Art Messenger unrestricted access when Data saver is on.\\nPix-Art Messenger will still make an effort to save data when possible.</string>
<string name="device_does_not_support_data_saver">Your device does not supporting disabling Data saver for Pix-Art Messenger.</string>
<string name="navigate">Navigate to location</string>
<string name="add_to_contact_list">Add to contact list</string>
<string name="received_contact">Received contact</string>
<string name="contact">Contact</string>
<string name="unable_to_start_recording">Unable to start recording</string>
<string name="error_unable_to_create_temporary_file">Error: unable to create temporary file</string>
<string name="create_issue">Report an issue</string>
<string name="pref_delete_omemo_identities">Delete OMEMO identities</string>
<string name="pref_delete_omemo_identities_summary">Regenerate your OMEMO keys. All your contacts will have to verify you again. Use this only as a last resort.</string>
<string name="delete_selected_keys">Delete selected keys</string>
<string name="missing_presence_subscription">Missing presence subscription</string>
<string name="missing_omemo_keys">Missing OMEMO keys</string>
<string name="error_publish_avatar_offline">You need to be connected to publish your avatar.</string>
<string name="select_text">Select text</string>
<string name="show_error_message">Show error message</string>
<string name="error_message">Error Message</string>
<string name="data_saver_enabled">Data saver enabled</string>
<string name="data_saver_enabled_explained">Your operating system is restricting Pix-Art Messenger from accessing the Internet when in background. To receive notifications of new messages you should allow Pix-Art Messenger unrestricted access when Data saver is on.\\nPix-Art Messenger will still make an effort to save data when possible.</string>
<string name="device_does_not_support_data_saver">Your device does not supporting disabling Data saver for Pix-Art Messenger.</string>
<string name="navigate">Navigate to location</string>
<string name="add_to_contact_list">Add to contact list</string>
<string name="received_contact">Received contact</string>
<string name="contact">Contact</string>
<string name="unable_to_start_recording">Unable to start recording</string>
<string name="error_unable_to_create_temporary_file">Unable to create temporary file</string>
<string name="this_device_has_been_verified">This device has been verified</string>
<string name="copy_fingerprint">Copy fingerprint</string>
</resources>