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 super.onActivityResult(requestCode, resultCode, data);
590 if (requestCode == REQUEST_BATTERY_OP || requestCode == REQUEST_DATA_SAVER) {
591 updateAccountInformation(mAccount == null);
592 }
593 if (requestCode == REQUEST_BATTERY_OP) {
594 // the result code is always 0 even when battery permission were granted
595 XmppConnectionService.toggleForegroundService(xmppConnectionService);
596 }
597 if (requestCode == REQUEST_CHANGE_STATUS) {
598 PresenceTemplate template = mPendingPresenceTemplate.pop();
599 if (template != null && resultCode == Activity.RESULT_OK) {
600 generateSignature(data, template);
601 } else {
602 Log.d(Config.LOGTAG, "pgp result not ok");
603 }
604 }
605 if (requestCode == REQUEST_UNLOCK) {
606 if (resultCode == RESULT_OK) {
607 openChangePassword(true);
608 }
609 }
610 }
611
612 @Override
613 protected void processFingerprintVerification(XmppUri uri) {
614 processFingerprintVerification(uri, true);
615 }
616
617 protected void processFingerprintVerification(XmppUri uri, boolean showWarningToast) {
618 if (mAccount != null
619 && mAccount.getJid().asBareJid().equals(uri.getJid())
620 && uri.hasFingerprints()) {
621 if (xmppConnectionService.verifyFingerprints(mAccount, uri.getFingerprints())) {
622 Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
623 updateAccountInformation(false);
624 }
625 } else if (showWarningToast) {
626 Toast.makeText(this, R.string.invalid_barcode, Toast.LENGTH_SHORT).show();
627 }
628 }
629
630 private void updatePortLayout() {
631 final String hostname = this.binding.hostname.getText().toString();
632 if (TextUtils.isEmpty(hostname)) {
633 this.binding.portLayout.setEnabled(false);
634 this.binding.portLayout.setError(null);
635 } else {
636 this.binding.portLayout.setEnabled(true);
637 }
638 }
639
640 protected void updateSaveButton() {
641 boolean accountInfoEdited = accountInfoEdited();
642
643 if (accountInfoEdited && !mInitMode) {
644 this.binding.saveButton.setText(R.string.save);
645 this.binding.saveButton.setEnabled(true);
646 } else if (mAccount != null
647 && (mAccount.getStatus() == Account.State.CONNECTING
648 || mAccount.getStatus() == Account.State.REGISTRATION_SUCCESSFUL
649 || mFetchingAvatar)) {
650 this.binding.saveButton.setEnabled(false);
651 this.binding.saveButton.setText(R.string.account_status_connecting);
652 } else if (mAccount != null
653 && mAccount.getStatus() == Account.State.DISABLED
654 && !mInitMode) {
655 this.binding.saveButton.setEnabled(true);
656 this.binding.saveButton.setText(R.string.enable);
657 } else if (torNeedsInstall(mAccount)) {
658 this.binding.saveButton.setEnabled(true);
659 this.binding.saveButton.setText(R.string.install_orbot);
660 } else if (torNeedsStart(mAccount)) {
661 this.binding.saveButton.setEnabled(true);
662 this.binding.saveButton.setText(R.string.start_orbot);
663 } else {
664 this.binding.saveButton.setEnabled(true);
665 if (!mInitMode) {
666 if (mAccount != null && mAccount.isOnlineAndConnected()) {
667 this.binding.saveButton.setText(R.string.save);
668 if (!accountInfoEdited) {
669 this.binding.saveButton.setEnabled(false);
670 }
671 } else {
672 XmppConnection connection =
673 mAccount == null ? null : mAccount.getXmppConnection();
674 HttpUrl url =
675 connection != null
676 && mAccount.getStatus()
677 == Account.State.PAYMENT_REQUIRED
678 ? connection.getRedirectionUrl()
679 : null;
680 if (url != null) {
681 this.binding.saveButton.setText(R.string.open_website);
682 } else if (inNeedOfSaslAccept()) {
683 this.binding.saveButton.setText(R.string.accept);
684 } else {
685 this.binding.saveButton.setText(R.string.connect);
686 }
687 }
688 } else {
689 XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
690 HttpUrl url =
691 connection != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB
692 ? connection.getRedirectionUrl()
693 : null;
694 if (url != null
695 && this.binding.accountRegisterNew.isChecked()
696 && !accountInfoEdited) {
697 this.binding.saveButton.setText(R.string.open_website);
698 } else {
699 this.binding.saveButton.setText(R.string.next);
700 }
701 }
702 }
703 }
704
705 private boolean torNeedsInstall(final Account account) {
706 return account != null
707 && account.getStatus() == Account.State.TOR_NOT_AVAILABLE
708 && !TorServiceUtils.isOrbotInstalled(this);
709 }
710
711 private boolean torNeedsStart(final Account account) {
712 return account != null && account.getStatus() == Account.State.TOR_NOT_AVAILABLE;
713 }
714
715 protected boolean accountInfoEdited() {
716 if (this.mAccount == null) {
717 return false;
718 }
719 ColorDrawable previewColor = (ColorDrawable) binding.colorPreview.getBackground();
720 return jidEdited()
721 || !this.mAccount
722 .getPassword()
723 .equals(this.binding.accountPassword.getText().toString())
724 || !this.mAccount.getHostname().equals(this.binding.hostname.getText().toString())
725 || this.mAccount.getColor(isDark()) != (previewColor == null ? 0 : previewColor.getColor())
726 || !String.valueOf(this.mAccount.getPort())
727 .equals(this.binding.port.getText().toString());
728 }
729
730 protected boolean jidEdited() {
731 final String unmodified;
732 if (mUsernameMode) {
733 unmodified = this.mAccount.getJid().getEscapedLocal();
734 } else {
735 unmodified = this.mAccount.getJid().asBareJid().toEscapedString();
736 }
737 return !unmodified.equals(this.binding.accountJid.getText().toString());
738 }
739
740 @Override
741 protected String getShareableUri(boolean http) {
742 if (mAccount != null) {
743 return http ? mAccount.getShareableLink() : mAccount.getShareableUri();
744 } else {
745 return null;
746 }
747 }
748
749 @Override
750 protected void onCreate(final Bundle savedInstanceState) {
751 super.onCreate(savedInstanceState);
752 if (savedInstanceState != null) {
753 this.mSavedInstanceAccount = savedInstanceState.getString("account");
754 this.mSavedInstanceInit = savedInstanceState.getBoolean("initMode", false);
755 }
756 this.binding = DataBindingUtil.setContentView(this, R.layout.activity_edit_account);
757 Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
758 setSupportActionBar(binding.toolbar);
759 binding.accountJid.addTextChangedListener(this.mTextWatcher);
760 binding.accountJid.setOnFocusChangeListener(this.mEditTextFocusListener);
761 this.binding.accountPassword.addTextChangedListener(this.mTextWatcher);
762
763 this.binding.avater.setOnClickListener(this.mAvatarClickListener);
764 this.binding.hostname.addTextChangedListener(mTextWatcher);
765 this.binding.hostname.setOnFocusChangeListener(mEditTextFocusListener);
766 this.binding.clearDevices.setOnClickListener(v -> showWipePepDialog());
767 this.binding.port.setText(String.valueOf(Resolver.DEFAULT_PORT_XMPP));
768 this.binding.port.addTextChangedListener(mTextWatcher);
769 this.binding.saveButton.setOnClickListener(this.mSaveButtonClickListener);
770 this.binding.cancelButton.setOnClickListener(this.mCancelButtonClickListener);
771 if (savedInstanceState != null && savedInstanceState.getBoolean("showMoreTable")) {
772 changeMoreTableVisibility(true);
773 }
774 final OnCheckedChangeListener OnCheckedShowConfirmPassword =
775 (buttonView, isChecked) -> updateSaveButton();
776 this.binding.accountRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword);
777 if (Config.DISALLOW_REGISTRATION_IN_UI) {
778 this.binding.accountRegisterNew.setVisibility(View.GONE);
779 }
780 this.binding.actionEditYourName.setOnClickListener(this::onEditYourNameClicked);
781 binding.accountColorBox.setOnClickListener((v) -> {
782 showColorDialog();
783 });
784
785 final var preferences = getPreferences();
786 binding.quietHoursEnable.setOnClickListener((v) -> {
787 preferences.edit().putBoolean("enable_quiet_hours:" + mAccount.getUuid(), binding.quietHoursEnable.isChecked()).apply();
788 updateAccountInformation(false);
789 });
790 binding.quietHoursStartBox.setOnClickListener((v) -> {
791 final var picker = new com.google.android.material.timepicker.MaterialTimePicker.Builder()
792 .setTitleText("Quiet Hours Start")
793 .setHour((int)(preferences.getLong("quiet_hours_start:" + mAccount.getUuid(), 1320) / 60))
794 .setMinute((int)(preferences.getLong("quiet_hours_start:" + mAccount.getUuid(), 1320) % 60))
795 .build();
796 picker.addOnPositiveButtonClickListener((v2) -> {
797 preferences.edit().putLong("quiet_hours_start:" + mAccount.getUuid(), (picker.getHour() * 60) + picker.getMinute()).apply();
798 updateAccountInformation(false);
799 });
800 picker.show(getSupportFragmentManager(), "quiethoursstart");
801 });
802 binding.quietHoursEndBox.setOnClickListener((v) -> {
803 final var picker = new com.google.android.material.timepicker.MaterialTimePicker.Builder()
804 .setTitleText("Quiet Hours End")
805 .setHour((int)(preferences.getLong("quiet_hours_end:" + mAccount.getUuid(), 1320) / 60))
806 .setMinute((int)(preferences.getLong("quiet_hours_end:" + mAccount.getUuid(), 1320) % 60))
807 .build();
808 picker.addOnPositiveButtonClickListener((v2) -> {
809 preferences.edit().putLong("quiet_hours_end:" + mAccount.getUuid(), (picker.getHour() * 60) + picker.getMinute()).apply();
810 updateAccountInformation(false);
811 });
812 picker.show(getSupportFragmentManager(), "quiethoursend");
813 });
814 this.binding.scanButton.setOnClickListener((v) -> ScanActivity.scan(this));
815 }
816
817 private void onEditYourNameClicked(View view) {
818 quickEdit(
819 mAccount.getDisplayName(),
820 R.string.your_name,
821 value -> {
822 final String displayName = value.trim();
823 updateDisplayName(displayName);
824 mAccount.setDisplayName(displayName);
825 xmppConnectionService.publishDisplayName(mAccount);
826 refreshAvatar();
827 return null;
828 },
829 true);
830 }
831
832 private void refreshAvatar() {
833 AvatarWorkerTask.loadAvatar(
834 mAccount, binding.avater, R.dimen.avatar_on_details_screen_size);
835 }
836
837 @Override
838 public boolean onCreateOptionsMenu(final Menu menu) {
839 super.onCreateOptionsMenu(menu);
840 getMenuInflater().inflate(R.menu.editaccount, menu);
841 final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list);
842 final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
843 final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
844 final MenuItem deleteAccount = menu.findItem(R.id.action_delete_account);
845 final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate);
846 final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs);
847 final MenuItem changePresence = menu.findItem(R.id.action_change_presence);
848 final MenuItem share = menu.findItem(R.id.action_share);
849 final MenuItem importBackup = menu.findItem(R.id.action_import_backup);
850 renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null);
851
852 share.setVisible(mAccount != null && !mInitMode);
853 importBackup.setVisible(mAccount == null || mInitMode);
854
855 if (mAccount != null && mAccount.isOnlineAndConnected()) {
856 if (!mAccount.getXmppConnection().getFeatures().blocking()) {
857 showBlocklist.setVisible(false);
858 }
859
860 if (!mAccount.getXmppConnection().getFeatures().register()) {
861 changePassword.setVisible(false);
862 deleteAccount.setVisible(false);
863 }
864 mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam());
865 changePresence.setVisible(!mInitMode);
866 } else {
867 showBlocklist.setVisible(false);
868 showMoreInfo.setVisible(false);
869 changePassword.setVisible(false);
870 deleteAccount.setVisible(false);
871 mamPrefs.setVisible(false);
872 changePresence.setVisible(false);
873 }
874 return super.onCreateOptionsMenu(menu);
875 }
876
877 @Override
878 public boolean onPrepareOptionsMenu(Menu menu) {
879 final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
880 if (showMoreInfo.isVisible()) {
881 showMoreInfo.setChecked(binding.serverInfoMore.getVisibility() == View.VISIBLE);
882 }
883 return super.onPrepareOptionsMenu(menu);
884 }
885
886 @Override
887 public void onStart() {
888 super.onStart();
889 final Intent intent = getIntent();
890 if (intent != null) {
891 try {
892 this.jidToEdit = Jid.ofEscaped(intent.getStringExtra("jid"));
893 } catch (final IllegalArgumentException | NullPointerException ignored) {
894 this.jidToEdit = null;
895 }
896 final Uri data = intent.getData();
897 final XmppUri xmppUri = data == null ? null : new XmppUri(data);
898 final boolean scanned = intent.getBooleanExtra("scanned", false);
899 if (jidToEdit != null && xmppUri != null && xmppUri.hasFingerprints()) {
900 if (scanned) {
901 if (xmppConnectionServiceBound) {
902 processFingerprintVerification(xmppUri, false);
903 } else {
904 this.pendingUri = xmppUri;
905 }
906 } else {
907 displayVerificationWarningDialog(xmppUri);
908 }
909 }
910 boolean init = intent.getBooleanExtra("init", false);
911 boolean openedFromNotification =
912 intent.getBooleanExtra(EXTRA_OPENED_FROM_NOTIFICATION, false);
913 Log.d(Config.LOGTAG, "extras " + intent.getExtras());
914 this.mForceRegister =
915 intent.hasExtra(EXTRA_FORCE_REGISTER)
916 ? intent.getBooleanExtra(EXTRA_FORCE_REGISTER, false)
917 : null;
918 Log.d(Config.LOGTAG, "force register=" + mForceRegister);
919 this.mInitMode = init || this.jidToEdit == null;
920 this.messageFingerprint = intent.getStringExtra("fingerprint");
921 if (!mInitMode) {
922 this.binding.accountRegisterNew.setVisibility(View.GONE);
923 setTitle(getString(R.string.account_details));
924 configureActionBar(getSupportActionBar(), !openedFromNotification);
925 } else {
926 this.binding.avater.setVisibility(View.GONE);
927 configureActionBar(
928 getSupportActionBar(), !(init && Config.MAGIC_CREATE_DOMAIN == null));
929 if (mForceRegister != null) {
930 if (mForceRegister) {
931 setTitle(R.string.register_new_account);
932 } else {
933 setTitle(R.string.add_existing_account);
934 }
935 } else {
936 setTitle(R.string.action_add_account);
937 }
938 }
939 }
940 SharedPreferences preferences = getPreferences();
941 mUseTor = preferences.getBoolean("use_tor", getResources().getBoolean(R.bool.use_tor));
942 this.mShowOptions = mUseTor || preferences.getBoolean("show_connection_options", getResources().getBoolean(R.bool.show_connection_options));
943 this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
944 if (mForceRegister != null) {
945 this.binding.accountRegisterNew.setVisibility(View.GONE);
946 }
947 if (intent.getBooleanExtra("snikket", false)) {
948 this.binding.accountJidLayout.setHint("Snikket Address");
949 }
950 }
951
952 private void displayVerificationWarningDialog(final XmppUri xmppUri) {
953 final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
954 builder.setTitle(R.string.verify_omemo_keys);
955 View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
956 final CheckBox isTrustedSource = view.findViewById(R.id.trusted_source);
957 TextView warning = view.findViewById(R.id.warning);
958 warning.setText(R.string.verifying_omemo_keys_trusted_source_account);
959 builder.setView(view);
960 builder.setPositiveButton(
961 R.string.continue_btn,
962 (dialog, which) -> {
963 if (isTrustedSource.isChecked()) {
964 processFingerprintVerification(xmppUri, false);
965 } else {
966 finish();
967 }
968 });
969 builder.setNegativeButton(R.string.cancel, (dialog, which) -> finish());
970 final var dialog = builder.create();
971 dialog.setCanceledOnTouchOutside(false);
972 dialog.setOnCancelListener(d -> finish());
973 dialog.show();
974 }
975
976 @Override
977 public void onNewIntent(final Intent intent) {
978 super.onNewIntent(intent);
979 if (intent != null && intent.getData() != null) {
980 final XmppUri uri = new XmppUri(intent.getData());
981 if (xmppConnectionServiceBound) {
982 processFingerprintVerification(uri, false);
983 } else {
984 this.pendingUri = uri;
985 }
986 }
987 }
988
989 @Override
990 public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
991 if (mAccount != null) {
992 savedInstanceState.putString(
993 "account", mAccount.getJid().asBareJid().toEscapedString());
994 savedInstanceState.putBoolean("initMode", mInitMode);
995 savedInstanceState.putBoolean(
996 "showMoreTable", binding.serverInfoMore.getVisibility() == View.VISIBLE);
997 }
998 super.onSaveInstanceState(savedInstanceState);
999 }
1000
1001 protected void onBackendConnected() {
1002 boolean init = true;
1003 if (mSavedInstanceAccount != null) {
1004 try {
1005 this.mAccount =
1006 xmppConnectionService.findAccountByJid(
1007 Jid.ofEscaped(mSavedInstanceAccount));
1008 this.mInitMode = mSavedInstanceInit;
1009 init = false;
1010 } catch (IllegalArgumentException e) {
1011 this.mAccount = null;
1012 }
1013
1014 } else if (this.jidToEdit != null) {
1015 this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit);
1016 }
1017
1018 if (mAccount != null) {
1019 this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER);
1020 this.mUsernameMode |=
1021 mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
1022 && mAccount.isOptionSet(Account.OPTION_REGISTER);
1023 if (mPendingFingerprintVerificationUri != null) {
1024 processFingerprintVerification(mPendingFingerprintVerificationUri, false);
1025 mPendingFingerprintVerificationUri = null;
1026 }
1027 updateAccountInformation(init);
1028 }
1029
1030 if (Config.MAGIC_CREATE_DOMAIN == null
1031 && this.xmppConnectionService.getAccounts().size() == 0) {
1032 this.binding.cancelButton.setEnabled(false);
1033 }
1034 if (mUsernameMode) {
1035 this.binding.accountJidLayout.setHint(getString(R.string.username_hint));
1036 } else {
1037 final KnownHostsAdapter mKnownHostsAdapter =
1038 new KnownHostsAdapter(
1039 this,
1040 R.layout.item_autocomplete,
1041 xmppConnectionService.getKnownHosts());
1042 this.binding.accountJid.setAdapter(mKnownHostsAdapter);
1043 }
1044
1045 if (pendingUri != null) {
1046 processFingerprintVerification(pendingUri, false);
1047 pendingUri = null;
1048 }
1049 updatePortLayout();
1050 updateSaveButton();
1051 invalidateOptionsMenu();
1052 }
1053
1054 private String getUserModeDomain() {
1055 if (mAccount != null && mAccount.getJid().getDomain() != null) {
1056 return mAccount.getServer();
1057 } else {
1058 return null;
1059 }
1060 }
1061
1062 @Override
1063 public boolean onOptionsItemSelected(final MenuItem item) {
1064 if (MenuDoubleTabUtil.shouldIgnoreTap()) {
1065 return false;
1066 }
1067 switch (item.getItemId()) {
1068 case android.R.id.home:
1069 deleteAccountAndReturnIfNecessary();
1070 break;
1071 case R.id.action_show_block_list:
1072 final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class);
1073 showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toEscapedString());
1074 startActivity(showBlocklistIntent);
1075 break;
1076 case R.id.action_server_info_show_more:
1077 changeMoreTableVisibility(!item.isChecked());
1078 break;
1079 case R.id.action_share_barcode:
1080 shareBarcode();
1081 break;
1082 case R.id.action_share_http:
1083 shareLink(true);
1084 break;
1085 case R.id.action_share_uri:
1086 shareLink(false);
1087 break;
1088 case R.id.action_change_password_on_server:
1089 gotoChangePassword();
1090 break;
1091 case R.id.action_delete_account:
1092 deleteAccount();
1093 break;
1094 case R.id.action_mam_prefs:
1095 editMamPrefs();
1096 break;
1097 case R.id.action_renew_certificate:
1098 renewCertificate();
1099 break;
1100 case R.id.action_change_presence:
1101 changePresence();
1102 break;
1103 case R.id.action_import_backup:
1104 if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
1105 startActivity(new Intent(this, ImportBackupActivity.class));
1106 }
1107 break;
1108 }
1109 return super.onOptionsItemSelected(item);
1110 }
1111
1112 private void deleteAccount() {
1113 this.deleteAccount(
1114 mAccount,
1115 () -> {
1116 finish();
1117 });
1118 }
1119
1120 private boolean inNeedOfSaslAccept() {
1121 return mAccount != null
1122 && mAccount.getLastErrorStatus() == Account.State.DOWNGRADE_ATTACK
1123 && mAccount.getPinnedMechanismPriority() >= 0
1124 && !accountInfoEdited();
1125 }
1126
1127 private void shareBarcode() {
1128 Intent intent = new Intent(Intent.ACTION_SEND);
1129 intent.putExtra(Intent.EXTRA_STREAM, BarcodeProvider.getUriForAccount(this, mAccount));
1130 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1131 intent.setType("image/png");
1132 startActivity(Intent.createChooser(intent, getText(R.string.share_with)));
1133 }
1134
1135 private void changeMoreTableVisibility(final boolean visible) {
1136 binding.serverInfoMore.setVisibility(visible ? View.VISIBLE : View.GONE);
1137 binding.serverInfoLoginMechanism.setVisibility(visible ? View.VISIBLE : View.GONE);
1138 }
1139
1140 private void gotoChangePassword() {
1141 KeyguardManager keyguardManager = (KeyguardManager) this.getSystemService(Context.KEYGUARD_SERVICE);
1142 Intent credentialsIntent = keyguardManager.createConfirmDeviceCredentialIntent("Unlock required", "Please unlock in order to change your password");
1143 if (credentialsIntent == null) {
1144 openChangePassword(false);
1145 } else {
1146 startActivityForResult(credentialsIntent, REQUEST_UNLOCK);
1147 }
1148 }
1149
1150 private void openChangePassword(boolean didUnlock) {
1151 final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class);
1152 changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toEscapedString());
1153 changePasswordIntent.putExtra("did_unlock", didUnlock);
1154 startActivity(changePasswordIntent);
1155 }
1156
1157 private void renewCertificate() {
1158 KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
1159 }
1160
1161 private void changePresence() {
1162 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1163 boolean manualStatus =
1164 sharedPreferences.getBoolean(
1165 AppSettings.MANUALLY_CHANGE_PRESENCE,
1166 getResources().getBoolean(R.bool.manually_change_presence));
1167 final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
1168 final DialogPresenceBinding binding =
1169 DataBindingUtil.inflate(getLayoutInflater(), R.layout.dialog_presence, null, false);
1170 String current = mAccount.getPresenceStatusMessage();
1171 if (current != null && !current.trim().isEmpty()) {
1172 binding.statusMessage.append(current);
1173 }
1174 setAvailabilityRadioButton(mAccount.getPresenceStatus(), binding);
1175 binding.show.setVisibility(manualStatus ? View.VISIBLE : View.GONE);
1176 List<PresenceTemplate> templates = xmppConnectionService.getPresenceTemplates(mAccount);
1177 PresenceTemplateAdapter presenceTemplateAdapter =
1178 new PresenceTemplateAdapter(this, R.layout.item_autocomplete, templates);
1179 binding.statusMessage.setAdapter(presenceTemplateAdapter);
1180 binding.statusMessage.setOnItemClickListener(
1181 (parent, view, position, id) -> {
1182 PresenceTemplate template =
1183 (PresenceTemplate) parent.getItemAtPosition(position);
1184 setAvailabilityRadioButton(template.getStatus(), binding);
1185 });
1186 builder.setTitle(R.string.edit_status_message_title);
1187 builder.setView(binding.getRoot());
1188 builder.setNegativeButton(R.string.cancel, null);
1189 builder.setPositiveButton(
1190 R.string.confirm,
1191 (dialog, which) -> {
1192 PresenceTemplate template =
1193 new PresenceTemplate(
1194 getAvailabilityRadioButton(binding),
1195 binding.statusMessage.getText().toString().trim());
1196 if (mAccount.getPgpId() != 0 && hasPgp()) {
1197 generateSignature(null, template);
1198 } else {
1199 xmppConnectionService.changeStatus(mAccount, template, null);
1200 }
1201 });
1202 builder.create().show();
1203 }
1204
1205 private void generateSignature(Intent intent, PresenceTemplate template) {
1206 xmppConnectionService
1207 .getPgpEngine()
1208 .generateSignature(
1209 intent,
1210 mAccount,
1211 template.getStatusMessage(),
1212 new UiCallback<String>() {
1213 @Override
1214 public void success(String signature) {
1215 xmppConnectionService.changeStatus(mAccount, template, signature);
1216 }
1217
1218 @Override
1219 public void error(int errorCode, String object) {}
1220
1221 @Override
1222 public void userInputRequired(PendingIntent pi, String object) {
1223 mPendingPresenceTemplate.push(template);
1224 try {
1225 startIntentSenderForResult(
1226 pi.getIntentSender(),
1227 REQUEST_CHANGE_STATUS,
1228 null,
1229 0,
1230 0,
1231 0,
1232 Compatibility.pgpStartIntentSenderOptions());
1233 } catch (final IntentSender.SendIntentException ignored) {
1234 }
1235 }
1236 });
1237 }
1238
1239 @Override
1240 public void alias(String alias) {
1241 if (alias != null) {
1242 xmppConnectionService.updateKeyInAccount(mAccount, alias);
1243 }
1244 }
1245
1246 void showColorDialog() {
1247 AlertDialog.Builder builder = new AlertDialog.Builder(this);
1248 final ColorPickerView picker = new ColorPickerView(this);
1249
1250 if (mAccount != null) picker.setColor(mAccount.getColor(isDark()));
1251 picker.showAlpha(true);
1252 picker.showHex(true);
1253 picker.showPreview(true);
1254 builder
1255 .setTitle(null)
1256 .setView(picker)
1257 .setPositiveButton(R.string.ok, (dialog, which) -> {
1258 final int color = picker.getColor();
1259 binding.colorPreview.setBackgroundColor(color);
1260 updateSaveButton();
1261 })
1262 .setNegativeButton(R.string.cancel, (dialog, which) -> {});
1263 builder.show();
1264 }
1265
1266 private void updateAccountInformation(boolean init) {
1267 if (init) {
1268 this.binding.accountJid.getEditableText().clear();
1269 if (mUsernameMode) {
1270 this.binding
1271 .accountJid
1272 .getEditableText()
1273 .append(this.mAccount.getJid().getEscapedLocal());
1274 } else {
1275 this.binding
1276 .accountJid
1277 .getEditableText()
1278 .append(this.mAccount.getJid().asBareJid().toEscapedString());
1279 }
1280 this.binding.accountPassword.getEditableText().clear();
1281 this.binding.accountPassword.getEditableText().append(this.mAccount.getPassword());
1282 this.binding.hostname.setText("");
1283 this.binding.hostname.getEditableText().append(this.mAccount.getHostname());
1284 this.binding.port.setText("");
1285 this.binding.port.getEditableText().append(String.valueOf(this.mAccount.getPort()));
1286 this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
1287 }
1288
1289 final var preferences = getPreferences();
1290 binding.quietHoursEnable.setChecked(preferences.getBoolean("enable_quiet_hours:" + mAccount.getUuid(), false));
1291
1292 if (binding.quietHoursEnable.isChecked()) {
1293 binding.quietHoursStartBox.setVisibility(View.VISIBLE);
1294 binding.quietHoursEndBox.setVisibility(View.VISIBLE);
1295 } else {
1296 binding.quietHoursStartBox.setVisibility(View.GONE);
1297 binding.quietHoursEndBox.setVisibility(View.GONE);
1298 }
1299
1300 final var startTime = preferences.getLong("quiet_hours_start:" + mAccount.getUuid(), 1320);
1301 final DateFormat dateFormat = android.text.format.DateFormat.getTimeFormat(this);
1302 final Date date = TimePreference.minutesToCalender(startTime).getTime();
1303 binding.quietHoursStart.setText(dateFormat.format(date.getTime()));
1304
1305 final var endTime = preferences.getLong("quiet_hours_end:" + mAccount.getUuid(), 480);
1306 final DateFormat dateFormatE = android.text.format.DateFormat.getTimeFormat(this);
1307 final Date dateE = TimePreference.minutesToCalender(endTime).getTime();
1308 binding.quietHoursEnd.setText(dateFormat.format(dateE.getTime()));
1309
1310 if (!mInitMode && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1311 this.binding.accountPassword.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
1312 }
1313
1314 final boolean editable =
1315 !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)
1316 && !mAccount.isOptionSet(Account.OPTION_FIXED_USERNAME)
1317 && QuickConversationsService.isConversations();
1318 this.binding.accountJid.setEnabled(editable);
1319 this.binding.accountJid.setFocusable(editable);
1320 this.binding.accountJid.setFocusableInTouchMode(editable);
1321 this.binding.accountJid.setCursorVisible(editable);
1322
1323 final String displayName = mAccount.getDisplayName();
1324 updateDisplayName(displayName);
1325
1326 if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() > 1) {
1327 binding.accountColorBox.setVisibility(View.VISIBLE);
1328 binding.colorPreview.setBackgroundColor(mAccount.getColor(isDark()));
1329 binding.quietHoursBox.setVisibility(View.VISIBLE);
1330 } else {
1331 binding.accountColorBox.setVisibility(View.GONE);
1332 binding.quietHoursBox.setVisibility(View.GONE);
1333 }
1334
1335 final boolean togglePassword =
1336 mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
1337 || !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY);
1338 final boolean neverLoggedIn =
1339 !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)
1340 && QuickConversationsService.isConversations();
1341 final boolean editPassword = mAccount.unauthorized() || neverLoggedIn;
1342
1343 this.binding.accountPasswordLayout.setPasswordVisibilityToggleEnabled(togglePassword);
1344
1345 this.binding.accountPassword.setFocusable(editPassword);
1346 this.binding.accountPassword.setFocusableInTouchMode(editPassword);
1347 this.binding.accountPassword.setCursorVisible(editPassword);
1348 this.binding.accountPassword.setEnabled(editPassword);
1349
1350 if (!mInitMode) {
1351 this.binding.avater.setVisibility(View.VISIBLE);
1352 AvatarWorkerTask.loadAvatar(
1353 mAccount, binding.avater, R.dimen.avatar_on_details_screen_size);
1354 } else {
1355 this.binding.avater.setVisibility(View.GONE);
1356 }
1357 this.binding.accountRegisterNew.setChecked(
1358 this.mAccount.isOptionSet(Account.OPTION_REGISTER));
1359 if (this.mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
1360 if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
1361 ActionBar actionBar = getSupportActionBar();
1362 if (actionBar != null) {
1363 actionBar.setTitle(R.string.create_account);
1364 }
1365 }
1366 this.binding.accountRegisterNew.setVisibility(View.GONE);
1367 } else if (this.mAccount.isOptionSet(Account.OPTION_REGISTER) && mForceRegister == null) {
1368 this.binding.accountRegisterNew.setVisibility(View.VISIBLE);
1369 } else {
1370 this.binding.accountRegisterNew.setVisibility(View.GONE);
1371 }
1372 if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
1373 final Features features = this.mAccount.getXmppConnection().getFeatures();
1374 this.binding.stats.setVisibility(View.VISIBLE);
1375 boolean showBatteryWarning = isOptimizingBattery();
1376 boolean showDataSaverWarning = isAffectedByDataSaver();
1377 showOsOptimizationWarning(showBatteryWarning, showDataSaverWarning);
1378 this.binding.sessionEst.setText(
1379 UIHelper.readableTimeDifferenceFull(
1380 this, this.mAccount.getXmppConnection().getLastSessionEstablished()));
1381 if (features.rosterVersioning()) {
1382 this.binding.serverInfoRosterVersion.setText(R.string.server_info_available);
1383 } else {
1384 this.binding.serverInfoRosterVersion.setText(R.string.server_info_unavailable);
1385 }
1386 if (features.carbons()) {
1387 this.binding.serverInfoCarbons.setText(R.string.server_info_available);
1388 } else {
1389 this.binding.serverInfoCarbons.setText(R.string.server_info_unavailable);
1390 }
1391 if (features.mam()) {
1392 this.binding.serverInfoMam.setText(R.string.server_info_available);
1393 } else {
1394 this.binding.serverInfoMam.setText(R.string.server_info_unavailable);
1395 }
1396 if (features.csi()) {
1397 this.binding.serverInfoCsi.setText(R.string.server_info_available);
1398 } else {
1399 this.binding.serverInfoCsi.setText(R.string.server_info_unavailable);
1400 }
1401 if (features.blocking()) {
1402 this.binding.serverInfoBlocking.setText(R.string.server_info_available);
1403 } else {
1404 this.binding.serverInfoBlocking.setText(R.string.server_info_unavailable);
1405 }
1406 if (features.sm()) {
1407 this.binding.serverInfoSm.setText(R.string.server_info_available);
1408 } else {
1409 this.binding.serverInfoSm.setText(R.string.server_info_unavailable);
1410 }
1411 if (features.externalServiceDiscovery()) {
1412 this.binding.serverInfoExternalService.setText(R.string.server_info_available);
1413 } else {
1414 this.binding.serverInfoExternalService.setText(R.string.server_info_unavailable);
1415 }
1416 if (features.bind2()) {
1417 this.binding.serverInfoBind2.setText(R.string.server_info_available);
1418 } else {
1419 this.binding.serverInfoBind2.setText(R.string.server_info_unavailable);
1420 }
1421 if (features.sasl2()) {
1422 this.binding.serverInfoSasl2.setText(R.string.server_info_available);
1423 } else {
1424 this.binding.serverInfoSasl2.setText(R.string.server_info_unavailable);
1425 }
1426 this.binding.loginMechanism.setText(Strings.nullToEmpty(features.loginMechanism()));
1427 if (features.pep()) {
1428 AxolotlService axolotlService = this.mAccount.getAxolotlService();
1429 if (axolotlService != null && axolotlService.isPepBroken()) {
1430 this.binding.serverInfoPep.setText(R.string.server_info_broken);
1431 } else if (features.pepPublishOptions() || features.pepOmemoWhitelisted()) {
1432 this.binding.serverInfoPep.setText(R.string.server_info_available);
1433 } else {
1434 this.binding.serverInfoPep.setText(R.string.server_info_partial);
1435 }
1436 } else {
1437 this.binding.serverInfoPep.setText(R.string.server_info_unavailable);
1438 }
1439 if (features.httpUpload(0)) {
1440 final long maxFileSize = features.getMaxHttpUploadSize();
1441 if (maxFileSize > 0) {
1442 this.binding.serverInfoHttpUpload.setText(
1443 UIHelper.filesizeToString(maxFileSize));
1444 } else {
1445 this.binding.serverInfoHttpUpload.setText(R.string.server_info_available);
1446 }
1447 } else {
1448 this.binding.serverInfoHttpUpload.setText(R.string.server_info_unavailable);
1449 }
1450
1451 this.binding.pushRow.setVisibility(
1452 xmppConnectionService.getPushManagementService().isStub()
1453 ? View.GONE
1454 : View.VISIBLE);
1455
1456 if (xmppConnectionService.getPushManagementService().available(mAccount)) {
1457 this.binding.serverInfoPush.setText(R.string.server_info_available);
1458 } else {
1459 this.binding.serverInfoPush.setText(R.string.server_info_unavailable);
1460 }
1461 final long pgpKeyId = this.mAccount.getPgpId();
1462 if (pgpKeyId != 0 && Config.supportOpenPgp()) {
1463 OnClickListener openPgp = view -> launchOpenKeyChain(pgpKeyId);
1464 OnClickListener delete = view -> showDeletePgpDialog();
1465 this.binding.pgpFingerprintBox.setVisibility(View.VISIBLE);
1466 this.binding.pgpFingerprint.setText(OpenPgpUtils.convertKeyIdToHex(pgpKeyId));
1467 this.binding.pgpFingerprint.setOnClickListener(openPgp);
1468 if ("pgp".equals(messageFingerprint)) {
1469 this.binding.pgpFingerprintDesc.setTextColor(
1470 MaterialColors.getColor(
1471 binding.pgpFingerprintDesc,
1472 com.google.android.material.R.attr.colorPrimaryVariant));
1473 }
1474 this.binding.pgpFingerprintDesc.setOnClickListener(openPgp);
1475 this.binding.actionDeletePgp.setOnClickListener(delete);
1476 } else {
1477 this.binding.pgpFingerprintBox.setVisibility(View.GONE);
1478 }
1479 final String ownAxolotlFingerprint =
1480 this.mAccount.getAxolotlService().getOwnFingerprint();
1481 if (ownAxolotlFingerprint != null && Config.supportOmemo()) {
1482 this.binding.axolotlFingerprintBox.setVisibility(View.VISIBLE);
1483 if (ownAxolotlFingerprint.equals(messageFingerprint)) {
1484 this.binding.ownFingerprintDesc.setTextColor(
1485 MaterialColors.getColor(
1486 binding.ownFingerprintDesc,
1487 com.google.android.material.R.attr.colorPrimaryVariant));
1488 this.binding.ownFingerprintDesc.setText(
1489 R.string.omemo_fingerprint_selected_message);
1490 } else {
1491 this.binding.ownFingerprintDesc.setTextColor(
1492 MaterialColors.getColor(
1493 binding.ownFingerprintDesc,
1494 com.google.android.material.R.attr.colorOnSurface));
1495 this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint);
1496 }
1497 this.binding.axolotlFingerprint.setText(
1498 CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2)));
1499 this.binding.showQrCodeButton.setVisibility(View.VISIBLE);
1500 this.binding.showQrCodeButton.setOnClickListener(v -> showQrCode());
1501 } else {
1502 this.binding.axolotlFingerprintBox.setVisibility(View.GONE);
1503 }
1504 boolean hasKeys = false;
1505 boolean showUnverifiedWarning = false;
1506 binding.otherDeviceKeys.removeAllViews();
1507 for (final XmppAxolotlSession session :
1508 mAccount.getAxolotlService().findOwnSessions()) {
1509 final FingerprintStatus trust = session.getTrust();
1510 if (!trust.isCompromised()) {
1511 boolean highlight = session.getFingerprint().equals(messageFingerprint);
1512 addFingerprintRow(binding.otherDeviceKeys, session, highlight);
1513 hasKeys = true;
1514 }
1515 if (trust.isUnverified()) {
1516 showUnverifiedWarning = true;
1517 }
1518 }
1519 if (hasKeys
1520 && Config.supportOmemo()) { // TODO: either the button should be visible if we
1521 // print an active device or the device list should
1522 // be fed with reactived devices
1523 this.binding.otherDeviceKeysCard.setVisibility(View.VISIBLE);
1524 Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
1525 if (otherDevices == null || otherDevices.isEmpty()) {
1526 binding.clearDevices.setVisibility(View.GONE);
1527 } else {
1528 binding.clearDevices.setVisibility(View.VISIBLE);
1529 }
1530 binding.unverifiedWarning.setVisibility(
1531 showUnverifiedWarning ? View.VISIBLE : View.GONE);
1532 binding.scanButton.setVisibility(showUnverifiedWarning ? View.VISIBLE : View.GONE);
1533 } else {
1534 this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
1535 }
1536 this.binding.verificationBox.setVisibility(View.VISIBLE);
1537 if (mAccount.getXmppConnection() != null && mAccount.getXmppConnection().resolverAuthenticated()) {
1538 if (mAccount.getXmppConnection().daneVerified()) {
1539 this.binding.verificationMessage.setText("DNSSEC + DANE Verified");
1540 this.binding.verificationIndicator.setImageResource(R.drawable.shield_verified);
1541 } else {
1542 this.binding.verificationMessage.setText("DNSSEC Verified");
1543 this.binding.verificationIndicator.setImageResource(R.drawable.shield);
1544 }
1545 } else {
1546 this.binding.verificationMessage.setText("Not DNSSEC Verified");
1547 this.binding.verificationIndicator.setImageResource(R.drawable.shield_question);
1548 }
1549 } else {
1550 final TextInputLayout errorLayout;
1551 if (this.mAccount.errorStatus()) {
1552 if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED
1553 || this.mAccount.getStatus() == Account.State.DOWNGRADE_ATTACK) {
1554 errorLayout = this.binding.accountPasswordLayout;
1555 } else if (mShowOptions
1556 && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND
1557 && this.binding.hostname.getText().length() > 0) {
1558 errorLayout = this.binding.hostnameLayout;
1559 } else {
1560 errorLayout = this.binding.accountJidLayout;
1561 }
1562 errorLayout.setError(getString(this.mAccount.getStatus().getReadableId()));
1563 if (init || !accountInfoEdited()) {
1564 errorLayout.requestFocus();
1565 }
1566 } else {
1567 errorLayout = null;
1568 }
1569 removeErrorsOnAllBut(errorLayout);
1570 this.binding.stats.setVisibility(View.GONE);
1571 this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
1572 this.binding.verificationBox.setVisibility(View.GONE);
1573 }
1574 }
1575
1576 private void updateDisplayName(String displayName) {
1577 if (TextUtils.isEmpty(displayName)) {
1578 this.binding.yourName.setText(R.string.no_name_set_instructions);
1579 this.binding.yourName.setTextColor(
1580 MaterialColors.getColor(
1581 binding.yourName,
1582 com.google.android.material.R.attr.colorOnSurfaceVariant));
1583 } else {
1584 this.binding.yourName.setText(displayName);
1585 this.binding.yourName.setTextColor(
1586 MaterialColors.getColor(
1587 binding.yourName,
1588 com.google.android.material.R.attr.colorOnSurfaceVariant));
1589 }
1590 }
1591
1592 private void removeErrorsOnAllBut(TextInputLayout exception) {
1593 if (this.binding.accountJidLayout != exception) {
1594 this.binding.accountJidLayout.setErrorEnabled(false);
1595 this.binding.accountJidLayout.setError(null);
1596 }
1597 if (this.binding.accountPasswordLayout != exception) {
1598 this.binding.accountPasswordLayout.setErrorEnabled(false);
1599 this.binding.accountPasswordLayout.setError(null);
1600 }
1601 if (this.binding.hostnameLayout != exception) {
1602 this.binding.hostnameLayout.setErrorEnabled(false);
1603 this.binding.hostnameLayout.setError(null);
1604 }
1605 if (this.binding.portLayout != exception) {
1606 this.binding.portLayout.setErrorEnabled(false);
1607 this.binding.portLayout.setError(null);
1608 }
1609 }
1610
1611 private void showDeletePgpDialog() {
1612 final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
1613 builder.setTitle(R.string.unpublish_pgp);
1614 builder.setMessage(R.string.unpublish_pgp_message);
1615 builder.setNegativeButton(R.string.cancel, null);
1616 builder.setPositiveButton(
1617 R.string.confirm,
1618 (dialogInterface, i) -> {
1619 mAccount.setPgpSignId(0);
1620 mAccount.unsetPgpSignature();
1621 xmppConnectionService.databaseBackend.updateAccount(mAccount);
1622 xmppConnectionService.sendPresence(mAccount);
1623 refreshUiReal();
1624 });
1625 builder.create().show();
1626 }
1627
1628 private void showOsOptimizationWarning(
1629 boolean showBatteryWarning, boolean showDataSaverWarning) {
1630 this.binding.osOptimization.setVisibility(
1631 showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE);
1632 if (showDataSaverWarning
1633 && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
1634 this.binding.osOptimizationHeadline.setText(R.string.data_saver_enabled);
1635 this.binding.osOptimizationBody.setText(
1636 getString(R.string.data_saver_enabled_explained, getString(R.string.app_name)));
1637 this.binding.osOptimizationDisable.setText(R.string.allow);
1638 this.binding.osOptimizationDisable.setOnClickListener(
1639 v -> {
1640 Intent intent =
1641 new Intent(
1642 Settings
1643 .ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS);
1644 Uri uri = Uri.parse("package:" + getPackageName());
1645 intent.setData(uri);
1646 try {
1647 startActivityForResult(intent, REQUEST_DATA_SAVER);
1648 } catch (ActivityNotFoundException e) {
1649 Toast.makeText(
1650 EditAccountActivity.this,
1651 getString(
1652 R.string.device_does_not_support_data_saver,
1653 getString(R.string.app_name)),
1654 Toast.LENGTH_SHORT)
1655 .show();
1656 }
1657 });
1658 } else if (showBatteryWarning) {
1659 this.binding.osOptimizationDisable.setText(R.string.disable);
1660 this.binding.osOptimizationHeadline.setText(R.string.battery_optimizations_enabled);
1661 this.binding.osOptimizationBody.setText(
1662 getString(
1663 R.string.battery_optimizations_enabled_explained,
1664 getString(R.string.app_name)));
1665 this.binding.osOptimizationDisable.setOnClickListener(
1666 v -> {
1667 Intent intent =
1668 new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
1669 Uri uri = Uri.parse("package:" + getPackageName());
1670 intent.setData(uri);
1671 try {
1672 startActivityForResult(intent, REQUEST_BATTERY_OP);
1673 } catch (ActivityNotFoundException e) {
1674 Toast.makeText(
1675 EditAccountActivity.this,
1676 R.string.device_does_not_support_battery_op,
1677 Toast.LENGTH_SHORT)
1678 .show();
1679 }
1680 });
1681 }
1682 }
1683
1684 public void showWipePepDialog() {
1685 final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
1686 builder.setTitle(getString(R.string.clear_other_devices));
1687 builder.setIconAttribute(android.R.attr.alertDialogIcon);
1688 builder.setMessage(getString(R.string.clear_other_devices_desc));
1689 builder.setNegativeButton(getString(R.string.cancel), null);
1690 builder.setPositiveButton(
1691 getString(R.string.accept),
1692 (dialog, which) -> mAccount.getAxolotlService().wipeOtherPepDevices());
1693 builder.create().show();
1694 }
1695
1696 private void editMamPrefs() {
1697 this.mFetchingMamPrefsToast =
1698 Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG);
1699 this.mFetchingMamPrefsToast.show();
1700 xmppConnectionService.fetchMamPreferences(mAccount, this);
1701 }
1702
1703 @Override
1704 public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
1705 refreshUi();
1706 }
1707
1708 @Override
1709 public void onCaptchaRequested(
1710 final Account account, final String id, final Data data, final Bitmap captcha) {
1711 runOnUiThread(
1712 () -> {
1713 if (mCaptchaDialog != null && mCaptchaDialog.isShowing()) {
1714 mCaptchaDialog.dismiss();
1715 }
1716 if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
1717 Log.d(Config.LOGTAG, "activity not running when captcha was requested");
1718 return;
1719 }
1720 final MaterialAlertDialogBuilder builder =
1721 new MaterialAlertDialogBuilder(EditAccountActivity.this);
1722 final View view = getLayoutInflater().inflate(R.layout.captcha, null);
1723 final ImageView imageView = view.findViewById(R.id.captcha);
1724 final EditText input = view.findViewById(R.id.input);
1725 imageView.setImageBitmap(captcha);
1726
1727 builder.setTitle(getString(R.string.captcha_required));
1728 builder.setView(view);
1729
1730 builder.setPositiveButton(
1731 getString(R.string.ok),
1732 (dialog, which) -> {
1733 String rc = input.getText().toString();
1734 data.put("username", account.getUsername());
1735 data.put("password", account.getPassword());
1736 data.put("ocr", rc);
1737 data.submit();
1738
1739 if (xmppConnectionServiceBound) {
1740 xmppConnectionService.sendCreateAccountWithCaptchaPacket(
1741 account, id, data);
1742 }
1743 });
1744 builder.setNegativeButton(
1745 getString(R.string.cancel),
1746 (dialog, which) -> {
1747 if (xmppConnectionService != null) {
1748 xmppConnectionService.sendCreateAccountWithCaptchaPacket(
1749 account, null, null);
1750 }
1751 });
1752
1753 builder.setOnCancelListener(
1754 dialog -> {
1755 if (xmppConnectionService != null) {
1756 xmppConnectionService.sendCreateAccountWithCaptchaPacket(
1757 account, null, null);
1758 }
1759 });
1760 mCaptchaDialog = builder.create();
1761 mCaptchaDialog.show();
1762 input.requestFocus();
1763 });
1764 }
1765
1766 public void onShowErrorToast(final int resId) {
1767 runOnUiThread(
1768 () -> Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show());
1769 }
1770
1771 @Override
1772 public void onPreferencesFetched(final Element prefs) {
1773 runOnUiThread(
1774 () -> {
1775 if (mFetchingMamPrefsToast != null) {
1776 mFetchingMamPrefsToast.cancel();
1777 }
1778 final MaterialAlertDialogBuilder builder =
1779 new MaterialAlertDialogBuilder(EditAccountActivity.this);
1780 builder.setTitle(R.string.server_side_mam_prefs);
1781 String defaultAttr = prefs.getAttribute("default");
1782 final List<String> defaults = Arrays.asList("never", "roster", "always");
1783 final AtomicInteger choice =
1784 new AtomicInteger(Math.max(0, defaults.indexOf(defaultAttr)));
1785 builder.setSingleChoiceItems(
1786 R.array.mam_prefs, choice.get(), (dialog, which) -> choice.set(which));
1787 builder.setNegativeButton(R.string.cancel, null);
1788 builder.setPositiveButton(
1789 R.string.ok,
1790 (dialog, which) -> {
1791 prefs.setAttribute("default", defaults.get(choice.get()));
1792 xmppConnectionService.pushMamPreferences(mAccount, prefs);
1793 });
1794 if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
1795 builder.create().show();
1796 }
1797 });
1798 }
1799
1800 @Override
1801 public void onPreferencesFetchFailed() {
1802 runOnUiThread(
1803 () -> {
1804 if (mFetchingMamPrefsToast != null) {
1805 mFetchingMamPrefsToast.cancel();
1806 }
1807 Toast.makeText(
1808 EditAccountActivity.this,
1809 R.string.unable_to_fetch_mam_prefs,
1810 Toast.LENGTH_LONG)
1811 .show();
1812 });
1813 }
1814
1815 @Override
1816 public void OnUpdateBlocklist(Status status) {
1817 refreshUi();
1818 }
1819
1820 @Override
1821 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
1822 if (grantResults.length > 0) {
1823 if (allGranted(grantResults)) {
1824 switch (requestCode) {
1825 case REQUEST_IMPORT_BACKUP:
1826 startActivity(new Intent(this, ImportBackupActivity.class));
1827 break;
1828 }
1829 } else {
1830 Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
1831 }
1832 }
1833 if (writeGranted(grantResults, permissions)) {
1834 if (xmppConnectionService != null) {
1835 xmppConnectionService.restartFileObserver();
1836 }
1837 }
1838 }
1839}