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