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