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