From 35fca6af27edb7c249814df4219edf96a2f24823 Mon Sep 17 00:00:00 2001 From: Christian Schneppe Date: Thu, 17 Nov 2016 21:30:31 +0100 Subject: refactor omemo fingerprint UI code --- art/ic_verified_fingerprint.svg | 54 +++++ art/render.rb | 1 + .../java/de/pixart/messenger/OmemoActivity.java | 237 +++++++++++++++++++++ .../messenger/ui/ContactDetailsActivity.java | 48 +---- .../pixart/messenger/ui/EditAccountActivity.java | 176 ++++++++------- .../de/pixart/messenger/ui/TrustKeysActivity.java | 16 +- .../java/de/pixart/messenger/ui/XmppActivity.java | 130 ----------- .../res/drawable-hdpi/ic_verified_fingerprint.png | Bin 0 -> 1336 bytes .../res/drawable-mdpi/ic_verified_fingerprint.png | Bin 0 -> 1032 bytes .../res/drawable-xhdpi/ic_verified_fingerprint.png | Bin 0 -> 1557 bytes .../drawable-xxhdpi/ic_verified_fingerprint.png | Bin 0 -> 2305 bytes .../drawable-xxxhdpi/ic_verified_fingerprint.png | Bin 0 -> 2794 bytes src/main/res/layout/contact_key.xml | 73 ++++--- src/main/res/menu/omemo_key_context.xml | 9 + src/main/res/values/ids.xml | 2 + src/main/res/values/strings.xml | 80 +++---- 16 files changed, 484 insertions(+), 342 deletions(-) create mode 100644 art/ic_verified_fingerprint.svg create mode 100644 src/main/java/de/pixart/messenger/OmemoActivity.java create mode 100644 src/main/res/drawable-hdpi/ic_verified_fingerprint.png create mode 100644 src/main/res/drawable-mdpi/ic_verified_fingerprint.png create mode 100644 src/main/res/drawable-xhdpi/ic_verified_fingerprint.png create mode 100644 src/main/res/drawable-xxhdpi/ic_verified_fingerprint.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_verified_fingerprint.png create mode 100644 src/main/res/menu/omemo_key_context.xml diff --git a/art/ic_verified_fingerprint.svg b/art/ic_verified_fingerprint.svg new file mode 100644 index 000000000..7ac6da02f --- /dev/null +++ b/art/ic_verified_fingerprint.svg @@ -0,0 +1,54 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/art/render.rb b/art/render.rb index 2d1e5abcd..92ef1ee11 100755 --- a/art/render.rb +++ b/art/render.rb @@ -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], diff --git a/src/main/java/de/pixart/messenger/OmemoActivity.java b/src/main/java/de/pixart/messenger/OmemoActivity.java new file mode 100644 index 000000000..c71e306df --- /dev/null +++ b/src/main/java/de/pixart/messenger/OmemoActivity.java @@ -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(); + } +} \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java index c3992c179..51b9b33ec 100644 --- a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java @@ -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 diff --git a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java index e82bc0c5c..b5e3c8a1c 100644 --- a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java +++ b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java @@ -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(); - } - } - }); - } - } - - - public void showRegenerateAxolotlKeyDialog() { + 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() { Builder builder = new Builder(this); builder.setTitle("Regenerate Key"); builder.setIconAttribute(android.R.attr.alertDialogIcon); diff --git a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java index b737e17f1..dd499433a 100644 --- a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java +++ b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java @@ -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 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 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); diff --git a/src/main/java/de/pixart/messenger/ui/XmppActivity.java b/src/main/java/de/pixart/messenger/ui/XmppActivity.java index 95dc62d7d..b9c62e516 100644 --- a/src/main/java/de/pixart/messenger/ui/XmppActivity.java +++ b/src/main/java/de/pixart/messenger/ui/XmppActivity.java @@ -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) { diff --git a/src/main/res/drawable-hdpi/ic_verified_fingerprint.png b/src/main/res/drawable-hdpi/ic_verified_fingerprint.png new file mode 100644 index 000000000..297dea0d0 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_verified_fingerprint.png differ diff --git a/src/main/res/drawable-mdpi/ic_verified_fingerprint.png b/src/main/res/drawable-mdpi/ic_verified_fingerprint.png new file mode 100644 index 000000000..250ea4cd4 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_verified_fingerprint.png differ diff --git a/src/main/res/drawable-xhdpi/ic_verified_fingerprint.png b/src/main/res/drawable-xhdpi/ic_verified_fingerprint.png new file mode 100644 index 000000000..ba8162601 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_verified_fingerprint.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_verified_fingerprint.png b/src/main/res/drawable-xxhdpi/ic_verified_fingerprint.png new file mode 100644 index 000000000..e86e8efcc Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_verified_fingerprint.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_verified_fingerprint.png b/src/main/res/drawable-xxxhdpi/ic_verified_fingerprint.png new file mode 100644 index 000000000..5c78ac272 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_verified_fingerprint.png differ diff --git a/src/main/res/layout/contact_key.xml b/src/main/res/layout/contact_key.xml index 53a599fd3..ec8ca1996 100644 --- a/src/main/res/layout/contact_key.xml +++ b/src/main/res/layout/contact_key.xml @@ -1,16 +1,17 @@ + android:layout_height="match_parent" + android:longClickable="true"> + android:paddingTop="8dp"> + android:fontFamily="monospace" + android:longClickable="true"/> + android:textSize="?attr/TextSizeInfo" + android:longClickable="true"/> - - + android:textSize="?attr/TextSizeInfo" + android:longClickable="true"/> + + - + + + \ No newline at end of file diff --git a/src/main/res/menu/omemo_key_context.xml b/src/main/res/menu/omemo_key_context.xml new file mode 100644 index 000000000..2e2fc5dac --- /dev/null +++ b/src/main/res/menu/omemo_key_context.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/src/main/res/values/ids.xml b/src/main/res/values/ids.xml index 0786006cf..84f9ac69e 100644 --- a/src/main/res/values/ids.xml +++ b/src/main/res/values/ids.xml @@ -4,4 +4,6 @@ + + \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 6e2c31120..75df2af53 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -101,13 +101,13 @@ XMPP resource The name this client identifies itself with Accept files in WiFi connections - When connected to wifi automatically accept files smaller than… - Accept files in mobile connections - When connected with mobile data automatically accept files smaller than… - Accept files in mobile roaming connections - When connected with mobile data in roaming automatically accept files smaller than… + When connected to wifi automatically accept files smaller than… + Accept files in mobile connections + When connected with mobile data automatically accept files smaller than… + Accept files in mobile roaming connections + When connected with mobile data in roaming automatically accept files smaller than… Attachments - Notification + Notification Notifications Notify when a new message arrives Vibrate @@ -116,13 +116,13 @@ Blink notification light when a new message arrives Ringtone Play sound when a new message arrives - Send crash reports + Send crash reports By sending in stack traces you are helping the ongoing development of Pix-Art Messenger Confirm Messages Let your contact know when you have received and read a message UI OpenKeychain reported an error - Accept + Accept An error has occurred Grant presence updates Preemptively grant and ask for presence subscription for contacts you created @@ -197,7 +197,7 @@ %d hours ago 1 day ago %d days ago - Encrypted message. Please install OpenKeychain to decrypt. + Encrypted message. Please install OpenKeychain to decrypt. Unknown OTR fingerprint OpenPGP encrypted messages found Your fingerprint @@ -273,7 +273,7 @@ Don’t save encrypted messages Warning: This could lead to message loss Expert settings - About Pix-Art Messenger + About Pix-Art Messenger Build and licensing information Pix-Art Messenger \n\nCopyright © 2014-2016 Christian Schneppe @@ -330,7 +330,7 @@ Received messages will be marked with a green tick if supported Colorize send button to indicate contact status Other - Automatically join conferences + Automatically join conferences Respect the autojoin flag in conference bookmarks OTR fingerprint copied to clipboard! OMEMO fingerprint copied to clipboard! @@ -338,7 +338,7 @@ This conference is members only You have been kicked from this conference The conference was shut down - using account %s + using account %s Checking %s on HTTP host You are not connected. Try again later Check %s size @@ -394,7 +394,7 @@ Enable notifications No conference server found Conference creation failed!s - Account avatar + Account avatar Copy OTR fingerprint to clipboard Copy OMEMO fingerprint to clipboard Regenerate OMEMO key @@ -471,7 +471,7 @@ Sending %s Offering %s Hide offline - is typing… + is typing… Typing notifications Let your contact know when you are writing a new message Send location @@ -552,7 +552,7 @@ Verified OMEMO key with certificate! Your device does not support the selection of client certificates! Changes - Hostname + Hostname Port Server- or .onion-Address This is not a valid port number @@ -644,13 +644,13 @@ Creating conference… 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? Import backup - Invite again + Invite again has invited you via 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... - Broadcast Last User Interaction + Broadcast Last User Interaction Let all your contacts know when use Pix-Art Messenger - Invite to Pix-Art Messenger - 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. + Invite to Pix-Art Messenger + 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. 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. Unable to connect to OpenKeychain Pix-Art Messenger has no permissions @@ -678,7 +678,7 @@ Prepare video for transmission. Please wait… Compressing video, please wait… Shared video with %s - No permission to access %s + No permission to access %s Remote server not found This file seems to be corrupt. Unable to update account @@ -687,23 +687,25 @@ This is not a private, non-anonymous conference. There are no members in this conference. Report this JID as sending unwanted messages. - Report an issue - Delete OMEMO identities - Regenerate your OMEMO keys. All your contacts will have to verify you again. Use this only as a last resort. - Delete selected keys - Missing presence subscription - Missing OMEMO keys - You need to be connected to publish your avatar. - Select text - Show error message - Error Message - Data saver enabled - 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. - Your device does not supporting disabling Data saver for Pix-Art Messenger. - Navigate to location - Add to contact list - Received contact - Contact - Unable to start recording - Error: unable to create temporary file + Report an issue + Delete OMEMO identities + Regenerate your OMEMO keys. All your contacts will have to verify you again. Use this only as a last resort. + Delete selected keys + Missing presence subscription + Missing OMEMO keys + You need to be connected to publish your avatar. + Select text + Show error message + Error Message + Data saver enabled + 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. + Your device does not supporting disabling Data saver for Pix-Art Messenger. + Navigate to location + Add to contact list + Received contact + Contact + Unable to start recording + Unable to create temporary file + This device has been verified + Copy fingerprint -- cgit v1.2.3