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