EditAccountActivity.java

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