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