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