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        if (!editPassword && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1206            this.binding.accountJid.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
1207            this.binding.accountPassword.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
1208        }
1209
1210        if (!mInitMode) {
1211            this.binding.avater.setVisibility(View.VISIBLE);
1212            AvatarWorkerTask.loadAvatar(
1213                    mAccount, binding.avater, R.dimen.avatar_on_details_screen_size);
1214        } else {
1215            this.binding.avater.setVisibility(View.GONE);
1216        }
1217        this.binding.accountRegisterNew.setChecked(
1218                this.mAccount.isOptionSet(Account.OPTION_REGISTER));
1219        if (this.mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
1220            if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
1221                ActionBar actionBar = getSupportActionBar();
1222                if (actionBar != null) {
1223                    actionBar.setTitle(R.string.create_account);
1224                }
1225            }
1226            this.binding.accountRegisterNew.setVisibility(View.GONE);
1227        } else if (this.mAccount.isOptionSet(Account.OPTION_REGISTER) && mForceRegister == null) {
1228            this.binding.accountRegisterNew.setVisibility(View.VISIBLE);
1229        } else {
1230            this.binding.accountRegisterNew.setVisibility(View.GONE);
1231        }
1232        if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
1233            final Features features = this.mAccount.getXmppConnection().getFeatures();
1234            this.binding.stats.setVisibility(View.VISIBLE);
1235            boolean showBatteryWarning = isOptimizingBattery();
1236            boolean showDataSaverWarning = isAffectedByDataSaver();
1237            showOsOptimizationWarning(showBatteryWarning, showDataSaverWarning);
1238            this.binding.sessionEst.setText(
1239                    UIHelper.readableTimeDifferenceFull(
1240                            this, this.mAccount.getXmppConnection().getLastSessionEstablished()));
1241            if (features.rosterVersioning()) {
1242                this.binding.serverInfoRosterVersion.setText(R.string.server_info_available);
1243            } else {
1244                this.binding.serverInfoRosterVersion.setText(R.string.server_info_unavailable);
1245            }
1246            if (features.carbons()) {
1247                this.binding.serverInfoCarbons.setText(R.string.server_info_available);
1248            } else {
1249                this.binding.serverInfoCarbons.setText(R.string.server_info_unavailable);
1250            }
1251            if (features.mam()) {
1252                this.binding.serverInfoMam.setText(R.string.server_info_available);
1253            } else {
1254                this.binding.serverInfoMam.setText(R.string.server_info_unavailable);
1255            }
1256            if (features.csi()) {
1257                this.binding.serverInfoCsi.setText(R.string.server_info_available);
1258            } else {
1259                this.binding.serverInfoCsi.setText(R.string.server_info_unavailable);
1260            }
1261            if (features.blocking()) {
1262                this.binding.serverInfoBlocking.setText(R.string.server_info_available);
1263            } else {
1264                this.binding.serverInfoBlocking.setText(R.string.server_info_unavailable);
1265            }
1266            if (features.sm()) {
1267                this.binding.serverInfoSm.setText(R.string.server_info_available);
1268            } else {
1269                this.binding.serverInfoSm.setText(R.string.server_info_unavailable);
1270            }
1271            if (features.externalServiceDiscovery()) {
1272                this.binding.serverInfoExternalService.setText(R.string.server_info_available);
1273            } else {
1274                this.binding.serverInfoExternalService.setText(R.string.server_info_unavailable);
1275            }
1276            if (features.bind2()) {
1277                this.binding.serverInfoBind2.setText(R.string.server_info_available);
1278            } else {
1279                this.binding.serverInfoBind2.setText(R.string.server_info_unavailable);
1280            }
1281            if (features.sasl2()) {
1282                this.binding.serverInfoSasl2.setText(R.string.server_info_available);
1283            } else {
1284                this.binding.serverInfoSasl2.setText(R.string.server_info_unavailable);
1285            }
1286            this.binding.loginMechanism.setText(Strings.nullToEmpty(features.loginMechanism()));
1287            if (features.pep()) {
1288                AxolotlService axolotlService = this.mAccount.getAxolotlService();
1289                if (axolotlService != null && axolotlService.isPepBroken()) {
1290                    this.binding.serverInfoPep.setText(R.string.server_info_broken);
1291                } else if (features.pepPublishOptions() || features.pepOmemoWhitelisted()) {
1292                    this.binding.serverInfoPep.setText(R.string.server_info_available);
1293                } else {
1294                    this.binding.serverInfoPep.setText(R.string.server_info_partial);
1295                }
1296            } else {
1297                this.binding.serverInfoPep.setText(R.string.server_info_unavailable);
1298            }
1299            if (features.httpUpload(0)) {
1300                final long maxFileSize = features.getMaxHttpUploadSize();
1301                if (maxFileSize > 0) {
1302                    this.binding.serverInfoHttpUpload.setText(
1303                            UIHelper.filesizeToString(maxFileSize));
1304                } else {
1305                    this.binding.serverInfoHttpUpload.setText(R.string.server_info_available);
1306                }
1307            } else {
1308                this.binding.serverInfoHttpUpload.setText(R.string.server_info_unavailable);
1309            }
1310
1311            this.binding.pushRow.setVisibility(
1312                    xmppConnectionService.getPushManagementService().isStub()
1313                            ? View.GONE
1314                            : View.VISIBLE);
1315
1316            if (xmppConnectionService.getPushManagementService().available(mAccount)) {
1317                this.binding.serverInfoPush.setText(R.string.server_info_available);
1318            } else {
1319                this.binding.serverInfoPush.setText(R.string.server_info_unavailable);
1320            }
1321            final long pgpKeyId = this.mAccount.getPgpId();
1322            if (pgpKeyId != 0 && Config.supportOpenPgp()) {
1323                OnClickListener openPgp = view -> launchOpenKeyChain(pgpKeyId);
1324                OnClickListener delete = view -> showDeletePgpDialog();
1325                this.binding.pgpFingerprintBox.setVisibility(View.VISIBLE);
1326                this.binding.pgpFingerprint.setText(OpenPgpUtils.convertKeyIdToHex(pgpKeyId));
1327                this.binding.pgpFingerprint.setOnClickListener(openPgp);
1328                if ("pgp".equals(messageFingerprint)) {
1329                    this.binding.pgpFingerprintDesc.setTextColor(
1330                            MaterialColors.getColor(
1331                                    binding.pgpFingerprintDesc,
1332                                    com.google.android.material.R.attr.colorPrimaryVariant));
1333                }
1334                this.binding.pgpFingerprintDesc.setOnClickListener(openPgp);
1335                this.binding.actionDeletePgp.setOnClickListener(delete);
1336            } else {
1337                this.binding.pgpFingerprintBox.setVisibility(View.GONE);
1338            }
1339            final String ownAxolotlFingerprint =
1340                    this.mAccount.getAxolotlService().getOwnFingerprint();
1341            if (ownAxolotlFingerprint != null && Config.supportOmemo()) {
1342                this.binding.axolotlFingerprintBox.setVisibility(View.VISIBLE);
1343                this.binding.axolotlFingerprintBox.setOnCreateContextMenuListener(
1344                        (menu, v, menuInfo) -> {
1345                            getMenuInflater().inflate(R.menu.omemo_key_context, menu);
1346                            menu.findItem(R.id.verify_scan).setVisible(false);
1347                            menu.findItem(R.id.distrust_key).setVisible(false);
1348                            this.mSelectedFingerprint = ownAxolotlFingerprint;
1349                        });
1350                if (ownAxolotlFingerprint.equals(messageFingerprint)) {
1351                    this.binding.ownFingerprintDesc.setTextColor(
1352                            MaterialColors.getColor(
1353                                    binding.ownFingerprintDesc,
1354                                    com.google.android.material.R.attr.colorPrimaryVariant));
1355                    this.binding.ownFingerprintDesc.setText(
1356                            R.string.omemo_fingerprint_selected_message);
1357                } else {
1358                    this.binding.ownFingerprintDesc.setTextColor(
1359                            MaterialColors.getColor(
1360                                    binding.ownFingerprintDesc,
1361                                    com.google.android.material.R.attr.colorOnSurface));
1362                    this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint);
1363                }
1364                this.binding.axolotlFingerprint.setText(
1365                        CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2)));
1366                this.binding.showQrCodeButton.setVisibility(View.VISIBLE);
1367                this.binding.showQrCodeButton.setOnClickListener(v -> showQrCode());
1368            } else {
1369                this.binding.axolotlFingerprintBox.setVisibility(View.GONE);
1370            }
1371            boolean hasKeys = false;
1372            boolean showUnverifiedWarning = false;
1373            binding.otherDeviceKeys.removeAllViews();
1374            for (final XmppAxolotlSession session :
1375                    mAccount.getAxolotlService().findOwnSessions()) {
1376                final FingerprintStatus trust = session.getTrust();
1377                if (!trust.isCompromised()) {
1378                    boolean highlight = session.getFingerprint().equals(messageFingerprint);
1379                    addFingerprintRow(binding.otherDeviceKeys, session, highlight);
1380                    hasKeys = true;
1381                }
1382                if (trust.isUnverified()) {
1383                    showUnverifiedWarning = true;
1384                }
1385            }
1386            if (hasKeys
1387                    && Config.supportOmemo()) { // TODO: either the button should be visible if we
1388                // print an active device or the device list should
1389                // be fed with reactivated devices
1390                this.binding.otherDeviceKeysCard.setVisibility(View.VISIBLE);
1391                Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
1392                if (otherDevices == null || otherDevices.isEmpty()) {
1393                    binding.clearDevices.setVisibility(View.GONE);
1394                } else {
1395                    binding.clearDevices.setVisibility(View.VISIBLE);
1396                }
1397                binding.unverifiedWarning.setVisibility(
1398                        showUnverifiedWarning ? View.VISIBLE : View.GONE);
1399                binding.scanButton.setVisibility(showUnverifiedWarning ? View.VISIBLE : View.GONE);
1400            } else {
1401                this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
1402            }
1403        } else {
1404            final TextInputLayout errorLayout;
1405            final var status = this.mAccount.getStatus();
1406            if (status.isError()
1407                    || Arrays.asList(
1408                                    Account.State.NO_INTERNET,
1409                                    Account.State.MISSING_INTERNET_PERMISSION)
1410                            .contains(status)) {
1411                if (status == Account.State.UNAUTHORIZED
1412                        || status == Account.State.DOWNGRADE_ATTACK) {
1413                    errorLayout = this.binding.accountPasswordLayout;
1414                } else if (mShowOptions
1415                        && status == Account.State.SERVER_NOT_FOUND
1416                        && this.binding.hostname.getText().length() > 0) {
1417                    errorLayout = this.binding.hostnameLayout;
1418                } else {
1419                    errorLayout = this.binding.accountJidLayout;
1420                }
1421                errorLayout.setError(getString(this.mAccount.getStatus().getReadableId()));
1422                if (init || !accountInfoEdited()) {
1423                    errorLayout.requestFocus();
1424                }
1425            } else {
1426                errorLayout = null;
1427            }
1428            removeErrorsOnAllBut(errorLayout);
1429            this.binding.stats.setVisibility(View.GONE);
1430            this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
1431        }
1432    }
1433
1434    private void updateDisplayName(String displayName) {
1435        if (TextUtils.isEmpty(displayName)) {
1436            this.binding.yourName.setText(R.string.no_name_set_instructions);
1437            this.binding.yourName.setTextColor(
1438                    MaterialColors.getColor(
1439                            binding.yourName,
1440                            com.google.android.material.R.attr.colorOnSurfaceVariant));
1441        } else {
1442            this.binding.yourName.setText(displayName);
1443            this.binding.yourName.setTextColor(
1444                    MaterialColors.getColor(
1445                            binding.yourName,
1446                            com.google.android.material.R.attr.colorOnSurfaceVariant));
1447        }
1448    }
1449
1450    private void removeErrorsOnAllBut(TextInputLayout exception) {
1451        if (this.binding.accountJidLayout != exception) {
1452            this.binding.accountJidLayout.setErrorEnabled(false);
1453            this.binding.accountJidLayout.setError(null);
1454        }
1455        if (this.binding.accountPasswordLayout != exception) {
1456            this.binding.accountPasswordLayout.setErrorEnabled(false);
1457            this.binding.accountPasswordLayout.setError(null);
1458        }
1459        if (this.binding.hostnameLayout != exception) {
1460            this.binding.hostnameLayout.setErrorEnabled(false);
1461            this.binding.hostnameLayout.setError(null);
1462        }
1463        if (this.binding.portLayout != exception) {
1464            this.binding.portLayout.setErrorEnabled(false);
1465            this.binding.portLayout.setError(null);
1466        }
1467    }
1468
1469    private void showDeletePgpDialog() {
1470        final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
1471        builder.setTitle(R.string.unpublish_pgp);
1472        builder.setMessage(R.string.unpublish_pgp_message);
1473        builder.setNegativeButton(R.string.cancel, null);
1474        builder.setPositiveButton(
1475                R.string.confirm,
1476                (dialogInterface, i) -> {
1477                    mAccount.setPgpSignId(0);
1478                    mAccount.unsetPgpSignature();
1479                    xmppConnectionService.databaseBackend.updateAccount(mAccount);
1480                    xmppConnectionService.sendPresence(mAccount);
1481                    refreshUiReal();
1482                });
1483        builder.create().show();
1484    }
1485
1486    private void showOsOptimizationWarning(
1487            boolean showBatteryWarning, boolean showDataSaverWarning) {
1488        this.binding.osOptimization.setVisibility(
1489                showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE);
1490        if (showDataSaverWarning
1491                && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
1492            this.binding.osOptimizationHeadline.setText(R.string.data_saver_enabled);
1493            this.binding.osOptimizationBody.setText(
1494                    getString(R.string.data_saver_enabled_explained, getString(R.string.app_name)));
1495            this.binding.osOptimizationDisable.setText(R.string.allow);
1496            this.binding.osOptimizationDisable.setOnClickListener(
1497                    v -> {
1498                        Intent intent =
1499                                new Intent(
1500                                        Settings
1501                                                .ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS);
1502                        Uri uri = Uri.parse("package:" + getPackageName());
1503                        intent.setData(uri);
1504                        try {
1505                            startActivityForResult(intent, REQUEST_DATA_SAVER);
1506                        } catch (ActivityNotFoundException e) {
1507                            Toast.makeText(
1508                                            EditAccountActivity.this,
1509                                            getString(
1510                                                    R.string.device_does_not_support_data_saver,
1511                                                    getString(R.string.app_name)),
1512                                            Toast.LENGTH_SHORT)
1513                                    .show();
1514                        }
1515                    });
1516        } else if (showBatteryWarning) {
1517            this.binding.osOptimizationDisable.setText(R.string.disable);
1518            this.binding.osOptimizationHeadline.setText(R.string.battery_optimizations_enabled);
1519            this.binding.osOptimizationBody.setText(
1520                    getString(
1521                            R.string.battery_optimizations_enabled_explained,
1522                            getString(R.string.app_name)));
1523            this.binding.osOptimizationDisable.setOnClickListener(
1524                    v -> {
1525                        Intent intent =
1526                                new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
1527                        Uri uri = Uri.parse("package:" + getPackageName());
1528                        intent.setData(uri);
1529                        try {
1530                            startActivityForResult(intent, REQUEST_BATTERY_OP);
1531                        } catch (ActivityNotFoundException e) {
1532                            Toast.makeText(
1533                                            EditAccountActivity.this,
1534                                            R.string.device_does_not_support_battery_op,
1535                                            Toast.LENGTH_SHORT)
1536                                    .show();
1537                        }
1538                    });
1539        }
1540    }
1541
1542    public void showWipePepDialog() {
1543        final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
1544        builder.setTitle(getString(R.string.clear_other_devices));
1545        builder.setIconAttribute(android.R.attr.alertDialogIcon);
1546        builder.setMessage(getString(R.string.clear_other_devices_desc));
1547        builder.setNegativeButton(getString(R.string.cancel), null);
1548        builder.setPositiveButton(
1549                getString(R.string.accept),
1550                (dialog, which) -> mAccount.getAxolotlService().wipeOtherPepDevices());
1551        builder.create().show();
1552    }
1553
1554    private void editMamPrefs() {
1555        this.mFetchingMamPrefsToast =
1556                Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG);
1557        this.mFetchingMamPrefsToast.show();
1558        xmppConnectionService.fetchMamPreferences(mAccount, this);
1559    }
1560
1561    @Override
1562    public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
1563        refreshUi();
1564    }
1565
1566    @Override
1567    public void onCaptchaRequested(
1568            final Account account, final String id, final Data data, final Bitmap captcha) {
1569        runOnUiThread(
1570                () -> {
1571                    if (mCaptchaDialog != null && mCaptchaDialog.isShowing()) {
1572                        mCaptchaDialog.dismiss();
1573                    }
1574                    if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
1575                        Log.d(Config.LOGTAG, "activity not running when captcha was requested");
1576                        return;
1577                    }
1578                    final MaterialAlertDialogBuilder builder =
1579                            new MaterialAlertDialogBuilder(EditAccountActivity.this);
1580                    final View view = getLayoutInflater().inflate(R.layout.captcha, null);
1581                    final ImageView imageView = view.findViewById(R.id.captcha);
1582                    final EditText input = view.findViewById(R.id.input);
1583                    imageView.setImageBitmap(captcha);
1584
1585                    builder.setTitle(getString(R.string.captcha_required));
1586                    builder.setView(view);
1587
1588                    builder.setPositiveButton(
1589                            getString(R.string.ok),
1590                            (dialog, which) -> {
1591                                String rc = input.getText().toString();
1592                                data.put("username", account.getUsername());
1593                                data.put("password", account.getPassword());
1594                                data.put("ocr", rc);
1595                                data.submit();
1596
1597                                if (xmppConnectionServiceBound) {
1598                                    xmppConnectionService.sendCreateAccountWithCaptchaPacket(
1599                                            account, id, data);
1600                                }
1601                            });
1602                    builder.setNegativeButton(
1603                            getString(R.string.cancel),
1604                            (dialog, which) -> {
1605                                if (xmppConnectionService != null) {
1606                                    xmppConnectionService.sendCreateAccountWithCaptchaPacket(
1607                                            account, null, null);
1608                                }
1609                            });
1610
1611                    builder.setOnCancelListener(
1612                            dialog -> {
1613                                if (xmppConnectionService != null) {
1614                                    xmppConnectionService.sendCreateAccountWithCaptchaPacket(
1615                                            account, null, null);
1616                                }
1617                            });
1618                    mCaptchaDialog = builder.create();
1619                    mCaptchaDialog.show();
1620                    input.requestFocus();
1621                });
1622    }
1623
1624    public void onShowErrorToast(final int resId) {
1625        runOnUiThread(
1626                () -> Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show());
1627    }
1628
1629    @Override
1630    public void onPreferencesFetched(final Element prefs) {
1631        runOnUiThread(
1632                () -> {
1633                    if (mFetchingMamPrefsToast != null) {
1634                        mFetchingMamPrefsToast.cancel();
1635                    }
1636                    final MaterialAlertDialogBuilder builder =
1637                            new MaterialAlertDialogBuilder(EditAccountActivity.this);
1638                    builder.setTitle(R.string.server_side_mam_prefs);
1639                    String defaultAttr = prefs.getAttribute("default");
1640                    final List<String> defaults = Arrays.asList("never", "roster", "always");
1641                    final AtomicInteger choice =
1642                            new AtomicInteger(Math.max(0, defaults.indexOf(defaultAttr)));
1643                    builder.setSingleChoiceItems(
1644                            R.array.mam_prefs, choice.get(), (dialog, which) -> choice.set(which));
1645                    builder.setNegativeButton(R.string.cancel, null);
1646                    builder.setPositiveButton(
1647                            R.string.ok,
1648                            (dialog, which) -> {
1649                                prefs.setAttribute("default", defaults.get(choice.get()));
1650                                xmppConnectionService.pushMamPreferences(mAccount, prefs);
1651                            });
1652                    if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
1653                        builder.create().show();
1654                    }
1655                });
1656    }
1657
1658    @Override
1659    public void onPreferencesFetchFailed() {
1660        runOnUiThread(
1661                () -> {
1662                    if (mFetchingMamPrefsToast != null) {
1663                        mFetchingMamPrefsToast.cancel();
1664                    }
1665                    Toast.makeText(
1666                                    EditAccountActivity.this,
1667                                    R.string.unable_to_fetch_mam_prefs,
1668                                    Toast.LENGTH_LONG)
1669                            .show();
1670                });
1671    }
1672
1673    @Override
1674    public void OnUpdateBlocklist(Status status) {
1675        refreshUi();
1676    }
1677}