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