EditAccountActivity.java

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