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