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