EditAccountActivity.java

   1package eu.siacs.conversations.ui;
   2
   3import android.app.Activity;
   4import android.app.PendingIntent;
   5import android.content.ActivityNotFoundException;
   6import android.content.DialogInterface;
   7import android.content.Intent;
   8import android.content.IntentSender;
   9import android.content.SharedPreferences;
  10import android.databinding.DataBindingUtil;
  11import android.graphics.Bitmap;
  12import android.net.Uri;
  13import android.os.Bundle;
  14import android.os.Handler;
  15import android.preference.PreferenceManager;
  16import android.provider.Settings;
  17import android.security.KeyChain;
  18import android.security.KeyChainAliasCallback;
  19import android.support.design.widget.TextInputLayout;
  20import android.support.v7.app.ActionBar;
  21import android.support.v7.app.AlertDialog;
  22import android.support.v7.app.AlertDialog.Builder;
  23import android.text.Editable;
  24import android.text.TextWatcher;
  25import android.util.Log;
  26import android.view.Menu;
  27import android.view.MenuItem;
  28import android.view.View;
  29import android.view.View.OnClickListener;
  30import android.widget.Button;
  31import android.widget.CheckBox;
  32import android.widget.CompoundButton;
  33import android.widget.CompoundButton.OnCheckedChangeListener;
  34import android.widget.EditText;
  35import android.widget.ImageButton;
  36import android.widget.ImageView;
  37import android.widget.LinearLayout;
  38import android.widget.RelativeLayout;
  39import android.widget.TableLayout;
  40import android.widget.TableRow;
  41import android.widget.TextView;
  42import android.widget.Toast;
  43
  44import org.openintents.openpgp.util.OpenPgpUtils;
  45
  46import java.net.URL;
  47import java.util.Arrays;
  48import java.util.List;
  49import java.util.Set;
  50import java.util.concurrent.atomic.AtomicInteger;
  51
  52import eu.siacs.conversations.Config;
  53import eu.siacs.conversations.R;
  54import eu.siacs.conversations.crypto.axolotl.AxolotlService;
  55import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
  56import eu.siacs.conversations.databinding.ActivityEditAccountBinding;
  57import eu.siacs.conversations.databinding.DialogPresenceBinding;
  58import eu.siacs.conversations.entities.Account;
  59import eu.siacs.conversations.entities.Presence;
  60import eu.siacs.conversations.entities.PresenceTemplate;
  61import eu.siacs.conversations.services.BarcodeProvider;
  62import eu.siacs.conversations.services.XmppConnectionService;
  63import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
  64import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested;
  65import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
  66import eu.siacs.conversations.ui.adapter.PresenceTemplateAdapter;
  67import eu.siacs.conversations.ui.util.PendingItem;
  68import eu.siacs.conversations.ui.widget.DisabledActionModeCallback;
  69import eu.siacs.conversations.utils.CryptoHelper;
  70import eu.siacs.conversations.utils.UIHelper;
  71import eu.siacs.conversations.utils.XmppUri;
  72import eu.siacs.conversations.xml.Element;
  73import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
  74import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
  75import eu.siacs.conversations.xmpp.XmppConnection;
  76import eu.siacs.conversations.xmpp.XmppConnection.Features;
  77import eu.siacs.conversations.xmpp.forms.Data;
  78import eu.siacs.conversations.xmpp.pep.Avatar;
  79import rocks.xmpp.addr.Jid;
  80
  81public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, OnUpdateBlocklist,
  82		OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched {
  83
  84	private static final int REQUEST_DATA_SAVER = 0xf244;
  85	private static final int REQUEST_CHANGE_STATUS = 0xee11;
  86	private TextInputLayout mAccountJidLayout;
  87	private EditText mPassword;
  88	private TextInputLayout mPasswordLayout;
  89	private Button mCancelButton;
  90	private Button mSaveButton;
  91	private Button mDisableOsOptimizationsButton;
  92	private TextView getmDisableOsOptimizationsBody;
  93	private TableLayout mMoreTable;
  94
  95	private TextView mAxolotlFingerprint;
  96	private TextView mPgpFingerprint;
  97	private TextView mOwnFingerprintDesc;
  98	private TextView getmPgpFingerprintDesc;
  99	private ImageView mAvatar;
 100	private RelativeLayout mAxolotlFingerprintBox;
 101	private RelativeLayout mPgpFingerprintBox;
 102	private ImageButton mAxolotlFingerprintToClipboardButton;
 103	private ImageButton mPgpDeleteFingerprintButton;
 104	private LinearLayout keys;
 105	private LinearLayout mNamePort;
 106	private EditText mHostname;
 107	private TextInputLayout mHostnameLayout;
 108	private EditText mPort;
 109	private TextInputLayout mPortLayout;
 110	private AlertDialog mCaptchaDialog = null;
 111
 112	private Jid jidToEdit;
 113	private boolean mInitMode = false;
 114	private boolean mUsernameMode = Config.DOMAIN_LOCK != null;
 115	private boolean mShowOptions = false;
 116	private Account mAccount;
 117	private String messageFingerprint;
 118
 119	private final PendingItem<PresenceTemplate> mPendingPresenceTemplate = new PendingItem<>();
 120
 121	private boolean mFetchingAvatar = false;
 122
 123	private final OnClickListener mSaveButtonClickListener = new OnClickListener() {
 124
 125		@Override
 126		public void onClick(final View v) {
 127			final String password = mPassword.getText().toString();
 128			final boolean wasDisabled = mAccount != null && mAccount.getStatus() == Account.State.DISABLED;
 129
 130			if (!mInitMode && passwordChangedInMagicCreateMode()) {
 131				gotoChangePassword(password);
 132				return;
 133			}
 134			if (mInitMode && mAccount != null) {
 135				mAccount.setOption(Account.OPTION_DISABLED, false);
 136			}
 137			if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited()) {
 138				mAccount.setOption(Account.OPTION_DISABLED, false);
 139				if (!xmppConnectionService.updateAccount(mAccount)) {
 140					Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
 141				}
 142				return;
 143			}
 144			final boolean registerNewAccount = binding.accountRegisterNew.isChecked() && !Config.DISALLOW_REGISTRATION_IN_UI;
 145			if (mUsernameMode && binding.accountJid.getText().toString().contains("@")) {
 146				mAccountJidLayout.setError(getString(R.string.invalid_username));
 147				removeErrorsOnAllBut(mAccountJidLayout);
 148				binding.accountJid.requestFocus();
 149				return;
 150			}
 151
 152			XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
 153			boolean openRegistrationUrl = registerNewAccount && mAccount != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB;
 154			boolean openPaymentUrl = mAccount != null && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED;
 155			final boolean redirectionWorthyStatus = openPaymentUrl || openRegistrationUrl;
 156			URL url = connection != null && redirectionWorthyStatus ? connection.getRedirectionUrl() : null;
 157			if (url != null && redirectionWorthyStatus && !wasDisabled) {
 158				try {
 159					startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url.toString())));
 160					return;
 161				} catch (ActivityNotFoundException e) {
 162					Toast.makeText(EditAccountActivity.this, R.string.application_found_to_open_website, Toast.LENGTH_SHORT);
 163					return;
 164				}
 165			}
 166
 167			final Jid jid;
 168			try {
 169				if (mUsernameMode) {
 170					jid = Jid.of(binding.accountJid.getText().toString(), getUserModeDomain(), null);
 171				} else {
 172					jid = Jid.of(binding.accountJid.getText().toString());
 173				}
 174			} catch (final IllegalArgumentException e) {
 175				if (mUsernameMode) {
 176					mAccountJidLayout.setError(getString(R.string.invalid_username));
 177				} else {
 178					mAccountJidLayout.setError(getString(R.string.invalid_jid));
 179				}
 180				binding.accountJid.requestFocus();
 181				removeErrorsOnAllBut(mAccountJidLayout);
 182				return;
 183			}
 184			String hostname = null;
 185			int numericPort = 5222;
 186			if (mShowOptions) {
 187				hostname = mHostname.getText().toString().replaceAll("\\s", "");
 188				final String port = mPort.getText().toString().replaceAll("\\s", "");
 189				if (hostname.contains(" ")) {
 190					mHostnameLayout.setError(getString(R.string.not_valid_hostname));
 191					mHostname.requestFocus();
 192					removeErrorsOnAllBut(mHostnameLayout);
 193					return;
 194				}
 195				try {
 196					numericPort = Integer.parseInt(port);
 197					if (numericPort < 0 || numericPort > 65535) {
 198						mPortLayout.setError(getString(R.string.not_a_valid_port));
 199						removeErrorsOnAllBut(mPortLayout);
 200						mPort.requestFocus();
 201						return;
 202					}
 203
 204				} catch (NumberFormatException e) {
 205					mPortLayout.setError(getString(R.string.not_a_valid_port));
 206					removeErrorsOnAllBut(mPortLayout);
 207					mPort.requestFocus();
 208					return;
 209				}
 210			}
 211
 212			if (jid.getLocal() == null) {
 213				if (mUsernameMode) {
 214					mAccountJidLayout.setError(getString(R.string.invalid_username));
 215				} else {
 216					mAccountJidLayout.setError(getString(R.string.invalid_jid));
 217				}
 218				removeErrorsOnAllBut(mAccountJidLayout);
 219				binding.accountJid.requestFocus();
 220				return;
 221			}
 222			if (mAccount != null) {
 223				if (mInitMode && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
 224					mAccount.setOption(Account.OPTION_MAGIC_CREATE, mAccount.getPassword().contains(password));
 225				}
 226				mAccount.setJid(jid);
 227				mAccount.setPort(numericPort);
 228				mAccount.setHostname(hostname);
 229				mAccountJidLayout.setError(null);
 230				mAccount.setPassword(password);
 231				mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
 232				if (!xmppConnectionService.updateAccount(mAccount)) {
 233					Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
 234					return;
 235				}
 236			} else {
 237				if (xmppConnectionService.findAccountByJid(jid) != null) {
 238					mAccountJidLayout.setError(getString(R.string.account_already_exists));
 239					removeErrorsOnAllBut(mAccountJidLayout);
 240					binding.accountJid.requestFocus();
 241					return;
 242				}
 243				mAccount = new Account(jid.asBareJid(), password);
 244				mAccount.setPort(numericPort);
 245				mAccount.setHostname(hostname);
 246				mAccount.setOption(Account.OPTION_USETLS, true);
 247				mAccount.setOption(Account.OPTION_USECOMPRESSION, true);
 248				mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
 249				xmppConnectionService.createAccount(mAccount);
 250			}
 251			mHostnameLayout.setError(null);
 252			mPortLayout.setError(null);
 253			if (mAccount.isEnabled()
 254					&& !registerNewAccount
 255					&& !mInitMode) {
 256				finish();
 257			} else {
 258				updateSaveButton();
 259				updateAccountInformation(true);
 260			}
 261
 262		}
 263	};
 264	private final OnClickListener mCancelButtonClickListener = new OnClickListener() {
 265
 266		@Override
 267		public void onClick(final View v) {
 268			deleteAccountAndReturnIfNecessary();
 269			finish();
 270		}
 271	};
 272	private Toast mFetchingMamPrefsToast;
 273	private String mSavedInstanceAccount;
 274	private boolean mSavedInstanceInit = false;
 275	private Button mClearDevicesButton;
 276	private XmppUri pendingUri = null;
 277	private boolean mUseTor;
 278	private ActivityEditAccountBinding binding;
 279
 280	public void refreshUiReal() {
 281		invalidateOptionsMenu();
 282		if (mAccount != null
 283				&& mAccount.getStatus() != Account.State.ONLINE
 284				&& mFetchingAvatar) {
 285			startActivity(new Intent(getApplicationContext(),
 286					ManageAccountActivity.class));
 287			finish();
 288		} else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) {
 289			if (!mFetchingAvatar) {
 290				mFetchingAvatar = true;
 291				xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback);
 292			}
 293		}
 294		if (mAccount != null) {
 295			updateAccountInformation(false);
 296		}
 297		updateSaveButton();
 298	}
 299
 300	@Override
 301	public boolean onNavigateUp() {
 302		deleteAccountAndReturnIfNecessary();
 303		return super.onNavigateUp();
 304	}
 305
 306	@Override
 307	public void onBackPressed() {
 308		deleteAccountAndReturnIfNecessary();
 309		super.onBackPressed();
 310	}
 311
 312	private void deleteAccountAndReturnIfNecessary() {
 313		if (mInitMode && mAccount != null && !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)) {
 314			xmppConnectionService.deleteAccount(mAccount);
 315		}
 316
 317		if (xmppConnectionService.getAccounts().size() == 0) {
 318			Intent intent = new Intent(EditAccountActivity.this, WelcomeActivity.class);
 319			WelcomeActivity.addInviteUri(intent, getIntent());
 320			startActivity(intent);
 321		}
 322	}
 323
 324	@Override
 325	public void onAccountUpdate() {
 326		refreshUi();
 327	}
 328
 329	private final UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() {
 330
 331		@Override
 332		public void userInputRequried(final PendingIntent pi, final Avatar avatar) {
 333			finishInitialSetup(avatar);
 334		}
 335
 336		@Override
 337		public void success(final Avatar avatar) {
 338			finishInitialSetup(avatar);
 339		}
 340
 341		@Override
 342		public void error(final int errorCode, final Avatar avatar) {
 343			finishInitialSetup(avatar);
 344		}
 345	};
 346	private final TextWatcher mTextWatcher = new TextWatcher() {
 347
 348		@Override
 349		public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
 350			updateSaveButton();
 351		}
 352
 353		@Override
 354		public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
 355		}
 356
 357		@Override
 358		public void afterTextChanged(final Editable s) {
 359
 360		}
 361	};
 362
 363	private View.OnFocusChangeListener mEditTextFocusListener = new View.OnFocusChangeListener() {
 364		@Override
 365		public void onFocusChange(View view, boolean b) {
 366			EditText et = (EditText) view;
 367			if (b) {
 368				int resId = mUsernameMode ? R.string.username : R.string.account_settings_example_jabber_id;
 369				if (view.getId() == R.id.hostname) {
 370					resId = mUseTor ? R.string.hostname_or_onion : R.string.hostname_example;
 371				}
 372				final int res = resId;
 373				new Handler().postDelayed(() -> et.setHint(res), 200);
 374			} else {
 375				et.setHint(null);
 376			}
 377		}
 378	};
 379
 380
 381	private final OnClickListener mAvatarClickListener = new OnClickListener() {
 382		@Override
 383		public void onClick(final View view) {
 384			if (mAccount != null) {
 385				final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
 386				intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString());
 387				startActivity(intent);
 388			}
 389		}
 390	};
 391
 392	protected void finishInitialSetup(final Avatar avatar) {
 393		runOnUiThread(() -> {
 394			hideKeyboard();
 395			final Intent intent;
 396			final XmppConnection connection = mAccount.getXmppConnection();
 397			final boolean wasFirstAccount = xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1;
 398			if (avatar != null || (connection != null && !connection.getFeatures().pep())) {
 399				intent = new Intent(getApplicationContext(), StartConversationActivity.class);
 400				if (wasFirstAccount) {
 401					intent.putExtra("init", true);
 402				}
 403			} else {
 404				intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
 405				intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString());
 406				intent.putExtra("setup", true);
 407			}
 408			if (wasFirstAccount) {
 409				intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
 410			}
 411			WelcomeActivity.addInviteUri(intent, getIntent());
 412			startActivity(intent);
 413			finish();
 414		});
 415	}
 416
 417	@Override
 418	public void onActivityResult(int requestCode, int resultCode, Intent data) {
 419		super.onActivityResult(requestCode, resultCode, data);
 420		if (requestCode == REQUEST_BATTERY_OP || requestCode == REQUEST_DATA_SAVER) {
 421			updateAccountInformation(mAccount == null);
 422		}
 423		if (requestCode == REQUEST_CHANGE_STATUS) {
 424			PresenceTemplate template = mPendingPresenceTemplate.pop();
 425			if (template != null && resultCode == Activity.RESULT_OK) {
 426				generateSignature(data,template);
 427			} else {
 428				Log.d(Config.LOGTAG,"pgp result not ok");
 429			}
 430		}
 431	}
 432
 433	@Override
 434	protected void processFingerprintVerification(XmppUri uri) {
 435		processFingerprintVerification(uri, true);
 436	}
 437
 438
 439	protected void processFingerprintVerification(XmppUri uri, boolean showWarningToast) {
 440		if (mAccount != null && mAccount.getJid().asBareJid().equals(uri.getJid()) && uri.hasFingerprints()) {
 441			if (xmppConnectionService.verifyFingerprints(mAccount, uri.getFingerprints())) {
 442				Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
 443			}
 444		} else if (showWarningToast) {
 445			Toast.makeText(this, R.string.invalid_barcode, Toast.LENGTH_SHORT).show();
 446		}
 447	}
 448
 449	protected void updateSaveButton() {
 450		boolean accountInfoEdited = accountInfoEdited();
 451
 452		if (!mInitMode && passwordChangedInMagicCreateMode()) {
 453			this.mSaveButton.setText(R.string.change_password);
 454			this.mSaveButton.setEnabled(true);
 455		} else if (accountInfoEdited && !mInitMode) {
 456			this.mSaveButton.setText(R.string.save);
 457			this.mSaveButton.setEnabled(true);
 458		} else if (mAccount != null
 459				&& (mAccount.getStatus() == Account.State.CONNECTING || mAccount.getStatus() == Account.State.REGISTRATION_SUCCESSFUL || mFetchingAvatar)) {
 460			this.mSaveButton.setEnabled(false);
 461			this.mSaveButton.setText(R.string.account_status_connecting);
 462		} else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !mInitMode) {
 463			this.mSaveButton.setEnabled(true);
 464			this.mSaveButton.setText(R.string.enable);
 465		} else {
 466			this.mSaveButton.setEnabled(true);
 467			if (!mInitMode) {
 468				if (mAccount != null && mAccount.isOnlineAndConnected()) {
 469					this.mSaveButton.setText(R.string.save);
 470					if (!accountInfoEdited) {
 471						this.mSaveButton.setEnabled(false);
 472					}
 473				} else {
 474					XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
 475					URL url = connection != null && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED ? connection.getRedirectionUrl() : null;
 476					if (url != null) {
 477						this.mSaveButton.setText(R.string.open_website);
 478					} else {
 479						this.mSaveButton.setText(R.string.connect);
 480					}
 481				}
 482			} else {
 483				XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
 484				URL url = connection != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB ? connection.getRedirectionUrl() : null;
 485				if (url != null && this.binding.accountRegisterNew.isChecked()) {
 486					this.mSaveButton.setText(R.string.open_website);
 487				} else {
 488					this.mSaveButton.setText(R.string.next);
 489				}
 490			}
 491		}
 492	}
 493
 494	protected boolean accountInfoEdited() {
 495		if (this.mAccount == null) {
 496			return false;
 497		}
 498		return jidEdited() ||
 499				!this.mAccount.getPassword().equals(this.mPassword.getText().toString()) ||
 500				!this.mAccount.getHostname().equals(this.mHostname.getText().toString()) ||
 501				!String.valueOf(this.mAccount.getPort()).equals(this.mPort.getText().toString());
 502	}
 503
 504	protected boolean jidEdited() {
 505		final String unmodified;
 506		if (mUsernameMode) {
 507			unmodified = this.mAccount.getJid().getLocal();
 508		} else {
 509			unmodified = this.mAccount.getJid().asBareJid().toString();
 510		}
 511		return !unmodified.equals(this.binding.accountJid.getText().toString());
 512	}
 513
 514	protected boolean passwordChangedInMagicCreateMode() {
 515		return mAccount != null
 516				&& mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
 517				&& !this.mAccount.getPassword().equals(this.mPassword.getText().toString())
 518				&& !this.jidEdited()
 519				&& mAccount.isOnlineAndConnected();
 520	}
 521
 522	@Override
 523	protected String getShareableUri(boolean http) {
 524		if (mAccount != null) {
 525			return http ? mAccount.getShareableLink() : mAccount.getShareableUri();
 526		} else {
 527			return null;
 528		}
 529	}
 530
 531	@Override
 532	protected void onCreate(final Bundle savedInstanceState) {
 533		super.onCreate(savedInstanceState);
 534		if (savedInstanceState != null) {
 535			this.mSavedInstanceAccount = savedInstanceState.getString("account");
 536			this.mSavedInstanceInit = savedInstanceState.getBoolean("initMode", false);
 537		}
 538		this.binding = DataBindingUtil.setContentView(this, R.layout.activity_edit_account);
 539		binding.accountJid.addTextChangedListener(this.mTextWatcher);
 540		binding.accountJid.setOnFocusChangeListener(this.mEditTextFocusListener);
 541		this.mAccountJidLayout = (TextInputLayout) findViewById(R.id.account_jid_layout);
 542		this.mPassword = (EditText) findViewById(R.id.account_password);
 543		this.mPassword.addTextChangedListener(this.mTextWatcher);
 544		this.mPasswordLayout = (TextInputLayout) findViewById(R.id.account_password_layout);
 545		this.mAvatar = (ImageView) findViewById(R.id.avater);
 546		this.mAvatar.setOnClickListener(this.mAvatarClickListener);
 547		this.mDisableOsOptimizationsButton = (Button) findViewById(R.id.os_optimization_disable);
 548		this.getmDisableOsOptimizationsBody = (TextView) findViewById(R.id.os_optimization_body);
 549		this.mPgpFingerprintBox = (RelativeLayout) findViewById(R.id.pgp_fingerprint_box);
 550		this.mPgpFingerprint = (TextView) findViewById(R.id.pgp_fingerprint);
 551		this.getmPgpFingerprintDesc = (TextView) findViewById(R.id.pgp_fingerprint_desc);
 552		this.mPgpDeleteFingerprintButton = (ImageButton) findViewById(R.id.action_delete_pgp);
 553		this.mAxolotlFingerprint = (TextView) findViewById(R.id.axolotl_fingerprint);
 554		this.mAxolotlFingerprintBox = (RelativeLayout) findViewById(R.id.axolotl_fingerprint_box);
 555		this.mAxolotlFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_axolotl_to_clipboard);
 556		this.mOwnFingerprintDesc = (TextView) findViewById(R.id.own_fingerprint_desc);
 557		this.keys = (LinearLayout) findViewById(R.id.other_device_keys);
 558		this.mNamePort = (LinearLayout) findViewById(R.id.name_port);
 559		this.mHostname = (EditText) findViewById(R.id.hostname);
 560		this.mHostname.addTextChangedListener(mTextWatcher);
 561		this.mHostname.setOnFocusChangeListener(mEditTextFocusListener);
 562		this.mHostnameLayout = (TextInputLayout) findViewById(R.id.hostname_layout);
 563		this.mClearDevicesButton = (Button) findViewById(R.id.clear_devices);
 564		this.mClearDevicesButton.setOnClickListener(v -> showWipePepDialog());
 565		this.mPort = (EditText) findViewById(R.id.port);
 566		this.mPort.setText("5222");
 567		this.mPort.addTextChangedListener(mTextWatcher);
 568		this.mPortLayout = (TextInputLayout) findViewById(R.id.port_layout);
 569		this.mSaveButton = (Button) findViewById(R.id.save_button);
 570		this.mCancelButton = (Button) findViewById(R.id.cancel_button);
 571		this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
 572		this.mCancelButton.setOnClickListener(this.mCancelButtonClickListener);
 573		this.mMoreTable = (TableLayout) findViewById(R.id.server_info_more);
 574		if (savedInstanceState != null && savedInstanceState.getBoolean("showMoreTable")) {
 575			changeMoreTableVisibility(true);
 576		}
 577		final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() {
 578			@Override
 579			public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
 580				updateSaveButton();
 581			}
 582		};
 583		this.binding.accountRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword);
 584		if (Config.DISALLOW_REGISTRATION_IN_UI) {
 585			this.binding.accountRegisterNew.setVisibility(View.GONE);
 586		}
 587	}
 588
 589	@Override
 590	public boolean onCreateOptionsMenu(final Menu menu) {
 591		super.onCreateOptionsMenu(menu);
 592		getMenuInflater().inflate(R.menu.editaccount, menu);
 593		final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list);
 594		final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
 595		final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
 596		final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate);
 597		final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs);
 598		final MenuItem changePresence = menu.findItem(R.id.action_change_presence);
 599		final MenuItem share = menu.findItem(R.id.action_share);
 600		renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null);
 601
 602		share.setVisible(mAccount != null && !mInitMode);
 603
 604		if (mAccount != null && mAccount.isOnlineAndConnected()) {
 605			if (!mAccount.getXmppConnection().getFeatures().blocking()) {
 606				showBlocklist.setVisible(false);
 607			}
 608
 609			if (!mAccount.getXmppConnection().getFeatures().register()) {
 610				changePassword.setVisible(false);
 611			}
 612			mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam());
 613			changePresence.setVisible(!mInitMode);
 614		} else {
 615			showBlocklist.setVisible(false);
 616			showMoreInfo.setVisible(false);
 617			changePassword.setVisible(false);
 618			mamPrefs.setVisible(false);
 619			changePresence.setVisible(false);
 620		}
 621		return super.onCreateOptionsMenu(menu);
 622	}
 623
 624	@Override
 625	public boolean onPrepareOptionsMenu(Menu menu) {
 626		final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
 627		if (showMoreInfo.isVisible()) {
 628			showMoreInfo.setChecked(mMoreTable.getVisibility() == View.VISIBLE);
 629		}
 630		return super.onPrepareOptionsMenu(menu);
 631	}
 632
 633	@Override
 634	protected void onStart() {
 635		super.onStart();
 636		final int theme = findTheme();
 637		if (this.mTheme != theme) {
 638			recreate();
 639		} else if (getIntent() != null) {
 640			try {
 641				this.jidToEdit = Jid.of(getIntent().getStringExtra("jid"));
 642			} catch (final IllegalArgumentException | NullPointerException ignored) {
 643				this.jidToEdit = null;
 644			}
 645			if (jidToEdit != null && getIntent().getData() != null) {
 646				final XmppUri uri = new XmppUri(getIntent().getData());
 647				if (xmppConnectionServiceBound) {
 648					processFingerprintVerification(uri, false);
 649				} else {
 650					this.pendingUri = uri;
 651				}
 652			}
 653			boolean init = getIntent().getBooleanExtra("init", false);
 654			this.mInitMode = init || this.jidToEdit == null;
 655			this.messageFingerprint = getIntent().getStringExtra("fingerprint");
 656			if (!mInitMode) {
 657				this.binding.accountRegisterNew.setVisibility(View.GONE);
 658				if (getSupportActionBar() != null) {
 659					getSupportActionBar().setTitle(getString(R.string.account_details));
 660				}
 661			} else {
 662				this.mAvatar.setVisibility(View.GONE);
 663				ActionBar ab = getSupportActionBar();
 664				if (ab != null) {
 665					if (init && Config.MAGIC_CREATE_DOMAIN == null) {
 666						ab.setDisplayShowHomeEnabled(false);
 667						ab.setDisplayHomeAsUpEnabled(false);
 668					}
 669					ab.setTitle(R.string.action_add_account);
 670				}
 671			}
 672		}
 673		SharedPreferences preferences = getPreferences();
 674		mUseTor = Config.FORCE_ORBOT || preferences.getBoolean("use_tor", false);
 675		this.mShowOptions = mUseTor || preferences.getBoolean("show_connection_options", false);
 676		this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
 677	}
 678
 679	@Override
 680	public void onNewIntent(Intent intent) {
 681		if (intent != null && intent.getData() != null) {
 682			final XmppUri uri = new XmppUri(intent.getData());
 683			if (xmppConnectionServiceBound) {
 684				processFingerprintVerification(uri, false);
 685			} else {
 686				this.pendingUri = uri;
 687			}
 688		}
 689	}
 690
 691	@Override
 692	public void onSaveInstanceState(final Bundle savedInstanceState) {
 693		if (mAccount != null) {
 694			savedInstanceState.putString("account", mAccount.getJid().asBareJid().toString());
 695			savedInstanceState.putBoolean("initMode", mInitMode);
 696			savedInstanceState.putBoolean("showMoreTable", mMoreTable.getVisibility() == View.VISIBLE);
 697		}
 698		super.onSaveInstanceState(savedInstanceState);
 699	}
 700
 701	protected void onBackendConnected() {
 702		boolean init = true;
 703		if (mSavedInstanceAccount != null) {
 704			try {
 705				this.mAccount = xmppConnectionService.findAccountByJid(Jid.of(mSavedInstanceAccount));
 706				this.mInitMode = mSavedInstanceInit;
 707				init = false;
 708			} catch (IllegalArgumentException e) {
 709				this.mAccount = null;
 710			}
 711
 712		} else if (this.jidToEdit != null) {
 713			this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit);
 714		}
 715
 716		if (mAccount != null) {
 717			this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER);
 718			this.mUsernameMode |= mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && mAccount.isOptionSet(Account.OPTION_REGISTER);
 719			if (this.mAccount.getPrivateKeyAlias() != null) {
 720				this.mPassword.setHint(R.string.authenticate_with_certificate);
 721				if (this.mInitMode) {
 722					this.mPassword.requestFocus();
 723				}
 724			}
 725			if (mPendingFingerprintVerificationUri != null) {
 726				processFingerprintVerification(mPendingFingerprintVerificationUri, false);
 727				mPendingFingerprintVerificationUri = null;
 728			}
 729			updateAccountInformation(init);
 730		}
 731
 732
 733		if (Config.MAGIC_CREATE_DOMAIN == null && this.xmppConnectionService.getAccounts().size() == 0) {
 734			this.mCancelButton.setEnabled(false);
 735		}
 736		if (mUsernameMode) {
 737			this.binding.accountJid.setHint(R.string.username_hint);
 738		} else {
 739			final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this,
 740					R.layout.simple_list_item,
 741					xmppConnectionService.getKnownHosts());
 742			this.binding.accountJid.setAdapter(mKnownHostsAdapter);
 743		}
 744
 745		if (pendingUri != null) {
 746			processFingerprintVerification(pendingUri, false);
 747			pendingUri = null;
 748		}
 749
 750		updateSaveButton();
 751		invalidateOptionsMenu();
 752	}
 753
 754	private String getUserModeDomain() {
 755		if (mAccount != null) {
 756			return mAccount.getJid().getDomain();
 757		} else {
 758			return Config.DOMAIN_LOCK;
 759		}
 760	}
 761
 762	@Override
 763	public boolean onOptionsItemSelected(final MenuItem item) {
 764		switch (item.getItemId()) {
 765			case R.id.action_show_block_list:
 766				final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class);
 767				showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
 768				startActivity(showBlocklistIntent);
 769				break;
 770			case R.id.action_server_info_show_more:
 771				changeMoreTableVisibility(!item.isChecked());
 772				break;
 773			case R.id.action_share_barcode:
 774				shareBarcode();
 775				break;
 776			case R.id.action_share_http:
 777				shareLink(true);
 778				break;
 779			case R.id.action_share_uri:
 780				shareLink(false);
 781				break;
 782			case R.id.action_change_password_on_server:
 783				gotoChangePassword(null);
 784				break;
 785			case R.id.action_mam_prefs:
 786				editMamPrefs();
 787				break;
 788			case R.id.action_renew_certificate:
 789				renewCertificate();
 790				break;
 791			case R.id.action_change_presence:
 792				changePresence();
 793				break;
 794		}
 795		return super.onOptionsItemSelected(item);
 796	}
 797
 798	private void shareBarcode() {
 799		Intent intent = new Intent(Intent.ACTION_SEND);
 800		intent.putExtra(Intent.EXTRA_STREAM, BarcodeProvider.getUriForAccount(this, mAccount));
 801		intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 802		intent.setType("image/png");
 803		startActivity(Intent.createChooser(intent, getText(R.string.share_with)));
 804	}
 805
 806	private void changeMoreTableVisibility(boolean visible) {
 807		mMoreTable.setVisibility(visible ? View.VISIBLE : View.GONE);
 808	}
 809
 810	private void gotoChangePassword(String newPassword) {
 811		final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class);
 812		changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
 813		if (newPassword != null) {
 814			changePasswordIntent.putExtra("password", newPassword);
 815		}
 816		startActivity(changePasswordIntent);
 817	}
 818
 819	private void renewCertificate() {
 820		KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
 821	}
 822
 823	private void changePresence() {
 824		SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
 825		boolean manualStatus = sharedPreferences.getBoolean(SettingsActivity.MANUALLY_CHANGE_PRESENCE, getResources().getBoolean(R.bool.manually_change_presence));
 826		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 827		final DialogPresenceBinding binding = DataBindingUtil.inflate(getLayoutInflater(),R.layout.dialog_presence,null,false);
 828		String current = mAccount.getPresenceStatusMessage();
 829		if (current != null && !current.trim().isEmpty()) {
 830			binding.statusMessage.append(current);
 831		}
 832		setAvailabilityRadioButton(mAccount.getPresenceStatus(),binding);
 833		binding.show.setVisibility(manualStatus ? View.VISIBLE : View.GONE);
 834		List<PresenceTemplate> templates = xmppConnectionService.getPresenceTemplates(mAccount);
 835		PresenceTemplateAdapter presenceTemplateAdapter = new PresenceTemplateAdapter(this,R.layout.simple_list_item,templates);
 836		binding.statusMessage.setAdapter(presenceTemplateAdapter);
 837		binding.statusMessage.setOnItemClickListener((parent, view, position, id) -> {
 838			PresenceTemplate template = (PresenceTemplate) parent.getItemAtPosition(position);
 839			setAvailabilityRadioButton(template.getStatus(), binding);
 840		});
 841		builder.setTitle(R.string.edit_status_message_title);
 842		builder.setView(binding.getRoot());
 843		builder.setNegativeButton(R.string.cancel,null);
 844		builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
 845			PresenceTemplate template = new PresenceTemplate(getAvailabilityRadioButton(binding),binding.statusMessage.getText().toString().trim());
 846			if (mAccount.getPgpId() != 0 && hasPgp()) {
 847				generateSignature(null, template);
 848			} else {
 849				xmppConnectionService.changeStatus(mAccount, template, null);
 850			}
 851		});
 852		builder.create().show();
 853	}
 854
 855	private void generateSignature(Intent intent, PresenceTemplate template) {
 856		xmppConnectionService.getPgpEngine().generateSignature(intent, mAccount, template.getStatusMessage(), new UiCallback<String>() {
 857			@Override
 858			public void success(String signature) {
 859				xmppConnectionService.changeStatus(mAccount,template,signature);
 860			}
 861
 862			@Override
 863			public void error(int errorCode, String object) {
 864
 865			}
 866
 867			@Override
 868			public void userInputRequried(PendingIntent pi, String object) {
 869				mPendingPresenceTemplate.push(template);
 870				try {
 871					startIntentSenderForResult(pi.getIntentSender(), REQUEST_CHANGE_STATUS, null, 0, 0, 0);
 872				} catch (final IntentSender.SendIntentException ignored) {
 873				}
 874			}
 875		});
 876	}
 877
 878	private static void setAvailabilityRadioButton(Presence.Status status, DialogPresenceBinding binding) {
 879		if (status == null) {
 880			binding.online.setChecked(true);
 881			return;
 882		}
 883		switch (status) {
 884			case DND:
 885				binding.dnd.setChecked(true);
 886				break;
 887			case XA:
 888				binding.xa.setChecked(true);
 889				break;
 890			case AWAY:
 891				binding.xa.setChecked(true);
 892				break;
 893			default:
 894				binding.online.setChecked(true);
 895		}
 896	}
 897
 898	private static Presence.Status getAvailabilityRadioButton(DialogPresenceBinding binding) {
 899		if (binding.dnd.isChecked()) {
 900			return Presence.Status.DND;
 901		} else if (binding.xa.isChecked()) {
 902			return Presence.Status.XA;
 903		} else if (binding.away.isChecked()) {
 904			return Presence.Status.AWAY;
 905		} else {
 906			return Presence.Status.ONLINE;
 907		}
 908	}
 909
 910	@Override
 911	public void alias(String alias) {
 912		if (alias != null) {
 913			xmppConnectionService.updateKeyInAccount(mAccount, alias);
 914		}
 915	}
 916
 917	private void updateAccountInformation(boolean init) {
 918		if (init) {
 919			this.binding.accountJid.getEditableText().clear();
 920			if (mUsernameMode) {
 921				this.binding.accountJid.getEditableText().append(this.mAccount.getJid().getLocal());
 922			} else {
 923				this.binding.accountJid.getEditableText().append(this.mAccount.getJid().asBareJid().toString());
 924			}
 925			this.mPassword.getEditableText().clear();
 926			this.mPassword.getEditableText().append(this.mAccount.getPassword());
 927			this.mHostname.setText("");
 928			this.mHostname.getEditableText().append(this.mAccount.getHostname());
 929			this.mPort.setText("");
 930			this.mPort.getEditableText().append(String.valueOf(this.mAccount.getPort()));
 931			this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
 932
 933		}
 934
 935		final boolean editable = !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY);
 936		this.binding.accountJid.setEnabled(editable);
 937		this.binding.accountJid.setFocusable(editable);
 938		this.binding.accountJid.setFocusableInTouchMode(editable);
 939
 940
 941		if (mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) || !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)) {
 942			this.binding.accountPasswordLayout.setPasswordVisibilityToggleEnabled(true);
 943		} else {
 944			this.binding.accountPasswordLayout.setPasswordVisibilityToggleEnabled(false);
 945		}
 946
 947		if (!mInitMode) {
 948			this.mAvatar.setVisibility(View.VISIBLE);
 949			this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(72)));
 950		} else {
 951			this.mAvatar.setVisibility(View.GONE);
 952		}
 953		this.binding.accountRegisterNew.setChecked(this.mAccount.isOptionSet(Account.OPTION_REGISTER));
 954		if (this.mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
 955			if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
 956				ActionBar actionBar = getSupportActionBar();
 957				if (actionBar != null) {
 958					actionBar.setTitle(R.string.create_account);
 959				}
 960			}
 961			this.binding.accountRegisterNew.setVisibility(View.GONE);
 962		} else if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
 963			this.binding.accountRegisterNew.setVisibility(View.VISIBLE);
 964		} else {
 965			this.binding.accountRegisterNew.setVisibility(View.GONE);
 966		}
 967		if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
 968			Features features = this.mAccount.getXmppConnection().getFeatures();
 969			this.binding.stats.setVisibility(View.VISIBLE);
 970			boolean showBatteryWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery();
 971			boolean showDataSaverWarning = isAffectedByDataSaver();
 972			showOsOptimizationWarning(showBatteryWarning, showDataSaverWarning);
 973			this.binding.sessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection()
 974					.getLastSessionEstablished()));
 975			if (features.rosterVersioning()) {
 976				this.binding.serverInfoRosterVersion.setText(R.string.server_info_available);
 977			} else {
 978				this.binding.serverInfoRosterVersion.setText(R.string.server_info_unavailable);
 979			}
 980			if (features.carbons()) {
 981				this.binding.serverInfoCarbons.setText(R.string.server_info_available);
 982			} else {
 983				this.binding.serverInfoCarbons.setText(R.string.server_info_unavailable);
 984			}
 985			if (features.mam()) {
 986				this.binding.serverInfoMam.setText(R.string.server_info_available);
 987			} else {
 988				this.binding.serverInfoMam.setText(R.string.server_info_unavailable);
 989			}
 990			if (features.csi()) {
 991				this.binding.serverInfoCsi.setText(R.string.server_info_available);
 992			} else {
 993				this.binding.serverInfoCsi.setText(R.string.server_info_unavailable);
 994			}
 995			if (features.blocking()) {
 996				this.binding.serverInfoBlocking.setText(R.string.server_info_available);
 997			} else {
 998				this.binding.serverInfoBlocking.setText(R.string.server_info_unavailable);
 999			}
1000			if (features.sm()) {
1001				this.binding.serverInfoSm.setText(R.string.server_info_available);
1002			} else {
1003				this.binding.serverInfoSm.setText(R.string.server_info_unavailable);
1004			}
1005			if (features.pep()) {
1006				AxolotlService axolotlService = this.mAccount.getAxolotlService();
1007				if (axolotlService != null && axolotlService.isPepBroken()) {
1008					this.binding.serverInfoPep.setText(R.string.server_info_broken);
1009				} else if (features.pepPublishOptions() || features.pepOmemoWhitelisted()) {
1010					this.binding.serverInfoPep.setText(R.string.server_info_available);
1011				} else {
1012					this.binding.serverInfoPep.setText(R.string.server_info_partial);
1013				}
1014			} else {
1015				this.binding.serverInfoPep.setText(R.string.server_info_unavailable);
1016			}
1017			if (features.httpUpload(0)) {
1018				this.binding.serverInfoHttpUpload.setText(R.string.server_info_available);
1019			} else {
1020				this.binding.serverInfoHttpUpload.setText(R.string.server_info_unavailable);
1021			}
1022
1023			this.binding.pushRow.setVisibility(xmppConnectionService.getPushManagementService().isStub() ? View.GONE : View.VISIBLE);
1024
1025			if (xmppConnectionService.getPushManagementService().available(mAccount)) {
1026				this.binding.serverInfoPush.setText(R.string.server_info_available);
1027			} else {
1028				this.binding.serverInfoPush.setText(R.string.server_info_unavailable);
1029			}
1030			final long pgpKeyId = this.mAccount.getPgpId();
1031			if (pgpKeyId != 0 && Config.supportOpenPgp()) {
1032				OnClickListener openPgp = view -> launchOpenKeyChain(pgpKeyId);
1033				OnClickListener delete = view -> showDeletePgpDialog();
1034				this.mPgpFingerprintBox.setVisibility(View.VISIBLE);
1035				this.mPgpFingerprint.setText(OpenPgpUtils.convertKeyIdToHex(pgpKeyId));
1036				this.mPgpFingerprint.setOnClickListener(openPgp);
1037				if ("pgp".equals(messageFingerprint)) {
1038					this.getmPgpFingerprintDesc.setTextAppearance(this,R.style.TextAppearance_Conversations_Caption_Highlight);
1039				}
1040				this.getmPgpFingerprintDesc.setOnClickListener(openPgp);
1041				this.mPgpDeleteFingerprintButton.setOnClickListener(delete);
1042			} else {
1043				this.mPgpFingerprintBox.setVisibility(View.GONE);
1044			}
1045			final String ownAxolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint();
1046			if (ownAxolotlFingerprint != null && Config.supportOmemo()) {
1047				this.mAxolotlFingerprintBox.setVisibility(View.VISIBLE);
1048				if (ownAxolotlFingerprint.equals(messageFingerprint)) {
1049					this.mOwnFingerprintDesc.setTextAppearance(this,R.style.TextAppearance_Conversations_Caption_Highlight);
1050					this.mOwnFingerprintDesc.setText(R.string.omemo_fingerprint_selected_message);
1051				} else {
1052					this.mOwnFingerprintDesc.setTextAppearance(this,R.style.TextAppearance_Conversations_Caption);
1053					this.mOwnFingerprintDesc.setText(R.string.omemo_fingerprint);
1054				}
1055				this.mAxolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2)));
1056				this.mAxolotlFingerprintToClipboardButton.setVisibility(View.VISIBLE);
1057				this.mAxolotlFingerprintToClipboardButton.setOnClickListener(v -> copyOmemoFingerprint(ownAxolotlFingerprint));
1058			} else {
1059				this.mAxolotlFingerprintBox.setVisibility(View.GONE);
1060			}
1061			boolean hasKeys = false;
1062			keys.removeAllViews();
1063			for (XmppAxolotlSession session : mAccount.getAxolotlService().findOwnSessions()) {
1064				if (!session.getTrust().isCompromised()) {
1065					boolean highlight = session.getFingerprint().equals(messageFingerprint);
1066					addFingerprintRow(keys, session, highlight);
1067					hasKeys = true;
1068				}
1069			}
1070			if (hasKeys && Config.supportOmemo()) {
1071				this.binding.otherDeviceKeysCard.setVisibility(View.VISIBLE);
1072				Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
1073				if (otherDevices == null || otherDevices.isEmpty()) {
1074					mClearDevicesButton.setVisibility(View.GONE);
1075				} else {
1076					mClearDevicesButton.setVisibility(View.VISIBLE);
1077				}
1078			} else {
1079				this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
1080			}
1081		} else {
1082			final TextInputLayout errorLayout;
1083			if (this.mAccount.errorStatus()) {
1084				if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) {
1085					errorLayout = this.mPasswordLayout;
1086				} else if (mShowOptions
1087						&& this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND
1088						&& this.mHostname.getText().length() > 0) {
1089					errorLayout = this.mHostnameLayout;
1090				} else {
1091					errorLayout = this.mAccountJidLayout;
1092				}
1093				errorLayout.setError(getString(this.mAccount.getStatus().getReadableId()));
1094				if (init || !accountInfoEdited()) {
1095					errorLayout.requestFocus();
1096				}
1097			} else {
1098				errorLayout = null;
1099			}
1100			removeErrorsOnAllBut(errorLayout);
1101			this.binding.stats.setVisibility(View.GONE);
1102			this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
1103		}
1104	}
1105
1106	private void removeErrorsOnAllBut(TextInputLayout exception) {
1107		if (this.mAccountJidLayout != exception) {
1108			this.mAccountJidLayout.setErrorEnabled(false);
1109			this.mAccountJidLayout.setError(null);
1110		}
1111		if (this.mPasswordLayout != exception) {
1112			this.mPasswordLayout.setErrorEnabled(false);
1113			this.mPasswordLayout.setError(null);
1114		}
1115		if (this.mHostnameLayout != exception) {
1116			this.mHostnameLayout.setErrorEnabled(false);
1117			this.mHostnameLayout.setError(null);
1118		}
1119		if (this.mPortLayout != exception) {
1120			this.mPortLayout.setErrorEnabled(false);
1121			this.mPortLayout.setError(null);
1122		}
1123	}
1124
1125	private void showDeletePgpDialog() {
1126		AlertDialog.Builder builder = new AlertDialog.Builder(this);
1127		builder.setTitle(R.string.unpublish_pgp);
1128		builder.setMessage(R.string.unpublish_pgp_message);
1129		builder.setNegativeButton(R.string.cancel, null);
1130		builder.setPositiveButton(R.string.confirm, (dialogInterface, i) -> {
1131			mAccount.setPgpSignId(0);
1132			mAccount.unsetPgpSignature();
1133			xmppConnectionService.databaseBackend.updateAccount(mAccount);
1134			xmppConnectionService.sendPresence(mAccount);
1135			refreshUiReal();
1136		});
1137		builder.create().show();
1138	}
1139
1140	private void showOsOptimizationWarning(boolean showBatteryWarning, boolean showDataSaverWarning) {
1141		this.binding.osOptimization.setVisibility(showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE);
1142		if (showDataSaverWarning) {
1143			this.binding.osOptimizationHeadline.setText(R.string.data_saver_enabled);
1144			this.getmDisableOsOptimizationsBody.setText(R.string.data_saver_enabled_explained);
1145			this.mDisableOsOptimizationsButton.setText(R.string.allow);
1146			this.mDisableOsOptimizationsButton.setOnClickListener(v -> {
1147				Intent intent = new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS);
1148				Uri uri = Uri.parse("package:" + getPackageName());
1149				intent.setData(uri);
1150				try {
1151					startActivityForResult(intent, REQUEST_DATA_SAVER);
1152				} catch (ActivityNotFoundException e) {
1153					Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_data_saver, Toast.LENGTH_SHORT).show();
1154				}
1155			});
1156		} else if (showBatteryWarning) {
1157			this.mDisableOsOptimizationsButton.setText(R.string.disable);
1158			this.binding.osOptimizationHeadline.setText(R.string.battery_optimizations_enabled);
1159			this.getmDisableOsOptimizationsBody.setText(R.string.battery_optimizations_enabled_explained);
1160			this.mDisableOsOptimizationsButton.setOnClickListener(v -> {
1161				Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
1162				Uri uri = Uri.parse("package:" + getPackageName());
1163				intent.setData(uri);
1164				try {
1165					startActivityForResult(intent, REQUEST_BATTERY_OP);
1166				} catch (ActivityNotFoundException e) {
1167					Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
1168				}
1169			});
1170		}
1171	}
1172
1173	public void showWipePepDialog() {
1174		Builder builder = new Builder(this);
1175		builder.setTitle(getString(R.string.clear_other_devices));
1176		builder.setIconAttribute(android.R.attr.alertDialogIcon);
1177		builder.setMessage(getString(R.string.clear_other_devices_desc));
1178		builder.setNegativeButton(getString(R.string.cancel), null);
1179		builder.setPositiveButton(getString(R.string.accept),
1180				(dialog, which) -> mAccount.getAxolotlService().wipeOtherPepDevices());
1181		builder.create().show();
1182	}
1183
1184	private void editMamPrefs() {
1185		this.mFetchingMamPrefsToast = Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG);
1186		this.mFetchingMamPrefsToast.show();
1187		xmppConnectionService.fetchMamPreferences(mAccount, this);
1188	}
1189
1190	@Override
1191	public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
1192		refreshUi();
1193	}
1194
1195	@Override
1196	public void onCaptchaRequested(final Account account, final String id, final Data data, final Bitmap captcha) {
1197		runOnUiThread(() -> {
1198			if (mCaptchaDialog != null && mCaptchaDialog.isShowing()) {
1199				mCaptchaDialog.dismiss();
1200			}
1201			final Builder builder = new Builder(EditAccountActivity.this);
1202			final View view = getLayoutInflater().inflate(R.layout.captcha, null);
1203			final ImageView imageView = view.findViewById(R.id.captcha);
1204			final EditText input = view.findViewById(R.id.input);
1205			imageView.setImageBitmap(captcha);
1206
1207			builder.setTitle(getString(R.string.captcha_required));
1208			builder.setView(view);
1209
1210			builder.setPositiveButton(getString(R.string.ok),
1211					(dialog, which) -> {
1212						String rc = input.getText().toString();
1213						data.put("username", account.getUsername());
1214						data.put("password", account.getPassword());
1215						data.put("ocr", rc);
1216						data.submit();
1217
1218						if (xmppConnectionServiceBound) {
1219							xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, id, data);
1220						}
1221					});
1222			builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> {
1223				if (xmppConnectionService != null) {
1224					xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
1225				}
1226			});
1227
1228			builder.setOnCancelListener(dialog -> {
1229				if (xmppConnectionService != null) {
1230					xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
1231				}
1232			});
1233			mCaptchaDialog = builder.create();
1234			mCaptchaDialog.show();
1235			input.requestFocus();
1236		});
1237	}
1238
1239	public void onShowErrorToast(final int resId) {
1240		runOnUiThread(() -> Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show());
1241	}
1242
1243	@Override
1244	public void onPreferencesFetched(final Element prefs) {
1245		runOnUiThread(() -> {
1246			if (mFetchingMamPrefsToast != null) {
1247				mFetchingMamPrefsToast.cancel();
1248			}
1249			Builder builder = new Builder(EditAccountActivity.this);
1250			builder.setTitle(R.string.server_side_mam_prefs);
1251			String defaultAttr = prefs.getAttribute("default");
1252			final List<String> defaults = Arrays.asList("never", "roster", "always");
1253			final AtomicInteger choice = new AtomicInteger(Math.max(0, defaults.indexOf(defaultAttr)));
1254			builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), (dialog, which) -> choice.set(which));
1255			builder.setNegativeButton(R.string.cancel, null);
1256			builder.setPositiveButton(R.string.ok, (dialog, which) -> {
1257				prefs.setAttribute("default", defaults.get(choice.get()));
1258				xmppConnectionService.pushMamPreferences(mAccount, prefs);
1259			});
1260			builder.create().show();
1261		});
1262	}
1263
1264	@Override
1265	public void onPreferencesFetchFailed() {
1266		runOnUiThread(() -> {
1267			if (mFetchingMamPrefsToast != null) {
1268				mFetchingMamPrefsToast.cancel();
1269			}
1270			Toast.makeText(EditAccountActivity.this, R.string.unable_to_fetch_mam_prefs, Toast.LENGTH_LONG).show();
1271		});
1272	}
1273
1274	@Override
1275	public void OnUpdateBlocklist(Status status) {
1276		refreshUi();
1277	}
1278}