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