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