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 if (!editPassword && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1206 this.binding.accountJid.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
1207 this.binding.accountPassword.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
1208 }
1209
1210 if (!mInitMode) {
1211 this.binding.avater.setVisibility(View.VISIBLE);
1212 AvatarWorkerTask.loadAvatar(
1213 mAccount, binding.avater, R.dimen.avatar_on_details_screen_size);
1214 } else {
1215 this.binding.avater.setVisibility(View.GONE);
1216 }
1217 this.binding.accountRegisterNew.setChecked(
1218 this.mAccount.isOptionSet(Account.OPTION_REGISTER));
1219 if (this.mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
1220 if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
1221 ActionBar actionBar = getSupportActionBar();
1222 if (actionBar != null) {
1223 actionBar.setTitle(R.string.create_account);
1224 }
1225 }
1226 this.binding.accountRegisterNew.setVisibility(View.GONE);
1227 } else if (this.mAccount.isOptionSet(Account.OPTION_REGISTER) && mForceRegister == null) {
1228 this.binding.accountRegisterNew.setVisibility(View.VISIBLE);
1229 } else {
1230 this.binding.accountRegisterNew.setVisibility(View.GONE);
1231 }
1232 if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
1233 final Features features = this.mAccount.getXmppConnection().getFeatures();
1234 this.binding.stats.setVisibility(View.VISIBLE);
1235 boolean showBatteryWarning = isOptimizingBattery();
1236 boolean showDataSaverWarning = isAffectedByDataSaver();
1237 showOsOptimizationWarning(showBatteryWarning, showDataSaverWarning);
1238 this.binding.sessionEst.setText(
1239 UIHelper.readableTimeDifferenceFull(
1240 this, this.mAccount.getXmppConnection().getLastSessionEstablished()));
1241 if (features.rosterVersioning()) {
1242 this.binding.serverInfoRosterVersion.setText(R.string.server_info_available);
1243 } else {
1244 this.binding.serverInfoRosterVersion.setText(R.string.server_info_unavailable);
1245 }
1246 if (features.carbons()) {
1247 this.binding.serverInfoCarbons.setText(R.string.server_info_available);
1248 } else {
1249 this.binding.serverInfoCarbons.setText(R.string.server_info_unavailable);
1250 }
1251 if (features.mam()) {
1252 this.binding.serverInfoMam.setText(R.string.server_info_available);
1253 } else {
1254 this.binding.serverInfoMam.setText(R.string.server_info_unavailable);
1255 }
1256 if (features.csi()) {
1257 this.binding.serverInfoCsi.setText(R.string.server_info_available);
1258 } else {
1259 this.binding.serverInfoCsi.setText(R.string.server_info_unavailable);
1260 }
1261 if (features.blocking()) {
1262 this.binding.serverInfoBlocking.setText(R.string.server_info_available);
1263 } else {
1264 this.binding.serverInfoBlocking.setText(R.string.server_info_unavailable);
1265 }
1266 if (features.sm()) {
1267 this.binding.serverInfoSm.setText(R.string.server_info_available);
1268 } else {
1269 this.binding.serverInfoSm.setText(R.string.server_info_unavailable);
1270 }
1271 if (features.externalServiceDiscovery()) {
1272 this.binding.serverInfoExternalService.setText(R.string.server_info_available);
1273 } else {
1274 this.binding.serverInfoExternalService.setText(R.string.server_info_unavailable);
1275 }
1276 if (features.bind2()) {
1277 this.binding.serverInfoBind2.setText(R.string.server_info_available);
1278 } else {
1279 this.binding.serverInfoBind2.setText(R.string.server_info_unavailable);
1280 }
1281 if (features.sasl2()) {
1282 this.binding.serverInfoSasl2.setText(R.string.server_info_available);
1283 } else {
1284 this.binding.serverInfoSasl2.setText(R.string.server_info_unavailable);
1285 }
1286 this.binding.loginMechanism.setText(Strings.nullToEmpty(features.loginMechanism()));
1287 if (features.pep()) {
1288 AxolotlService axolotlService = this.mAccount.getAxolotlService();
1289 if (axolotlService != null && axolotlService.isPepBroken()) {
1290 this.binding.serverInfoPep.setText(R.string.server_info_broken);
1291 } else if (features.pepPublishOptions() || features.pepOmemoWhitelisted()) {
1292 this.binding.serverInfoPep.setText(R.string.server_info_available);
1293 } else {
1294 this.binding.serverInfoPep.setText(R.string.server_info_partial);
1295 }
1296 } else {
1297 this.binding.serverInfoPep.setText(R.string.server_info_unavailable);
1298 }
1299 if (features.httpUpload(0)) {
1300 final long maxFileSize = features.getMaxHttpUploadSize();
1301 if (maxFileSize > 0) {
1302 this.binding.serverInfoHttpUpload.setText(
1303 UIHelper.filesizeToString(maxFileSize));
1304 } else {
1305 this.binding.serverInfoHttpUpload.setText(R.string.server_info_available);
1306 }
1307 } else {
1308 this.binding.serverInfoHttpUpload.setText(R.string.server_info_unavailable);
1309 }
1310
1311 this.binding.pushRow.setVisibility(
1312 xmppConnectionService.getPushManagementService().isStub()
1313 ? View.GONE
1314 : View.VISIBLE);
1315
1316 if (xmppConnectionService.getPushManagementService().available(mAccount)) {
1317 this.binding.serverInfoPush.setText(R.string.server_info_available);
1318 } else {
1319 this.binding.serverInfoPush.setText(R.string.server_info_unavailable);
1320 }
1321 final long pgpKeyId = this.mAccount.getPgpId();
1322 if (pgpKeyId != 0 && Config.supportOpenPgp()) {
1323 OnClickListener openPgp = view -> launchOpenKeyChain(pgpKeyId);
1324 OnClickListener delete = view -> showDeletePgpDialog();
1325 this.binding.pgpFingerprintBox.setVisibility(View.VISIBLE);
1326 this.binding.pgpFingerprint.setText(OpenPgpUtils.convertKeyIdToHex(pgpKeyId));
1327 this.binding.pgpFingerprint.setOnClickListener(openPgp);
1328 if ("pgp".equals(messageFingerprint)) {
1329 this.binding.pgpFingerprintDesc.setTextColor(
1330 MaterialColors.getColor(
1331 binding.pgpFingerprintDesc,
1332 com.google.android.material.R.attr.colorPrimaryVariant));
1333 }
1334 this.binding.pgpFingerprintDesc.setOnClickListener(openPgp);
1335 this.binding.actionDeletePgp.setOnClickListener(delete);
1336 } else {
1337 this.binding.pgpFingerprintBox.setVisibility(View.GONE);
1338 }
1339 final String ownAxolotlFingerprint =
1340 this.mAccount.getAxolotlService().getOwnFingerprint();
1341 if (ownAxolotlFingerprint != null && Config.supportOmemo()) {
1342 this.binding.axolotlFingerprintBox.setVisibility(View.VISIBLE);
1343 this.binding.axolotlFingerprintBox.setOnCreateContextMenuListener(
1344 (menu, v, menuInfo) -> {
1345 getMenuInflater().inflate(R.menu.omemo_key_context, menu);
1346 menu.findItem(R.id.verify_scan).setVisible(false);
1347 menu.findItem(R.id.distrust_key).setVisible(false);
1348 this.mSelectedFingerprint = ownAxolotlFingerprint;
1349 });
1350 if (ownAxolotlFingerprint.equals(messageFingerprint)) {
1351 this.binding.ownFingerprintDesc.setTextColor(
1352 MaterialColors.getColor(
1353 binding.ownFingerprintDesc,
1354 com.google.android.material.R.attr.colorPrimaryVariant));
1355 this.binding.ownFingerprintDesc.setText(
1356 R.string.omemo_fingerprint_selected_message);
1357 } else {
1358 this.binding.ownFingerprintDesc.setTextColor(
1359 MaterialColors.getColor(
1360 binding.ownFingerprintDesc,
1361 com.google.android.material.R.attr.colorOnSurface));
1362 this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint);
1363 }
1364 this.binding.axolotlFingerprint.setText(
1365 CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2)));
1366 this.binding.showQrCodeButton.setVisibility(View.VISIBLE);
1367 this.binding.showQrCodeButton.setOnClickListener(v -> showQrCode());
1368 } else {
1369 this.binding.axolotlFingerprintBox.setVisibility(View.GONE);
1370 }
1371 boolean hasKeys = false;
1372 boolean showUnverifiedWarning = false;
1373 binding.otherDeviceKeys.removeAllViews();
1374 for (final XmppAxolotlSession session :
1375 mAccount.getAxolotlService().findOwnSessions()) {
1376 final FingerprintStatus trust = session.getTrust();
1377 if (!trust.isCompromised()) {
1378 boolean highlight = session.getFingerprint().equals(messageFingerprint);
1379 addFingerprintRow(binding.otherDeviceKeys, session, highlight);
1380 hasKeys = true;
1381 }
1382 if (trust.isUnverified()) {
1383 showUnverifiedWarning = true;
1384 }
1385 }
1386 if (hasKeys
1387 && Config.supportOmemo()) { // TODO: either the button should be visible if we
1388 // print an active device or the device list should
1389 // be fed with reactivated devices
1390 this.binding.otherDeviceKeysCard.setVisibility(View.VISIBLE);
1391 Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
1392 if (otherDevices == null || otherDevices.isEmpty()) {
1393 binding.clearDevices.setVisibility(View.GONE);
1394 } else {
1395 binding.clearDevices.setVisibility(View.VISIBLE);
1396 }
1397 binding.unverifiedWarning.setVisibility(
1398 showUnverifiedWarning ? View.VISIBLE : View.GONE);
1399 binding.scanButton.setVisibility(showUnverifiedWarning ? View.VISIBLE : View.GONE);
1400 } else {
1401 this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
1402 }
1403 } else {
1404 final TextInputLayout errorLayout;
1405 final var status = this.mAccount.getStatus();
1406 if (status.isError()
1407 || Arrays.asList(
1408 Account.State.NO_INTERNET,
1409 Account.State.MISSING_INTERNET_PERMISSION)
1410 .contains(status)) {
1411 if (status == Account.State.UNAUTHORIZED
1412 || status == Account.State.DOWNGRADE_ATTACK) {
1413 errorLayout = this.binding.accountPasswordLayout;
1414 } else if (mShowOptions
1415 && status == Account.State.SERVER_NOT_FOUND
1416 && this.binding.hostname.getText().length() > 0) {
1417 errorLayout = this.binding.hostnameLayout;
1418 } else {
1419 errorLayout = this.binding.accountJidLayout;
1420 }
1421 errorLayout.setError(getString(this.mAccount.getStatus().getReadableId()));
1422 if (init || !accountInfoEdited()) {
1423 errorLayout.requestFocus();
1424 }
1425 } else {
1426 errorLayout = null;
1427 }
1428 removeErrorsOnAllBut(errorLayout);
1429 this.binding.stats.setVisibility(View.GONE);
1430 this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
1431 }
1432 }
1433
1434 private void updateDisplayName(String displayName) {
1435 if (TextUtils.isEmpty(displayName)) {
1436 this.binding.yourName.setText(R.string.no_name_set_instructions);
1437 this.binding.yourName.setTextColor(
1438 MaterialColors.getColor(
1439 binding.yourName,
1440 com.google.android.material.R.attr.colorOnSurfaceVariant));
1441 } else {
1442 this.binding.yourName.setText(displayName);
1443 this.binding.yourName.setTextColor(
1444 MaterialColors.getColor(
1445 binding.yourName,
1446 com.google.android.material.R.attr.colorOnSurfaceVariant));
1447 }
1448 }
1449
1450 private void removeErrorsOnAllBut(TextInputLayout exception) {
1451 if (this.binding.accountJidLayout != exception) {
1452 this.binding.accountJidLayout.setErrorEnabled(false);
1453 this.binding.accountJidLayout.setError(null);
1454 }
1455 if (this.binding.accountPasswordLayout != exception) {
1456 this.binding.accountPasswordLayout.setErrorEnabled(false);
1457 this.binding.accountPasswordLayout.setError(null);
1458 }
1459 if (this.binding.hostnameLayout != exception) {
1460 this.binding.hostnameLayout.setErrorEnabled(false);
1461 this.binding.hostnameLayout.setError(null);
1462 }
1463 if (this.binding.portLayout != exception) {
1464 this.binding.portLayout.setErrorEnabled(false);
1465 this.binding.portLayout.setError(null);
1466 }
1467 }
1468
1469 private void showDeletePgpDialog() {
1470 final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
1471 builder.setTitle(R.string.unpublish_pgp);
1472 builder.setMessage(R.string.unpublish_pgp_message);
1473 builder.setNegativeButton(R.string.cancel, null);
1474 builder.setPositiveButton(
1475 R.string.confirm,
1476 (dialogInterface, i) -> {
1477 mAccount.setPgpSignId(0);
1478 mAccount.unsetPgpSignature();
1479 xmppConnectionService.databaseBackend.updateAccount(mAccount);
1480 xmppConnectionService.sendPresence(mAccount);
1481 refreshUiReal();
1482 });
1483 builder.create().show();
1484 }
1485
1486 private void showOsOptimizationWarning(
1487 boolean showBatteryWarning, boolean showDataSaverWarning) {
1488 this.binding.osOptimization.setVisibility(
1489 showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE);
1490 if (showDataSaverWarning
1491 && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
1492 this.binding.osOptimizationHeadline.setText(R.string.data_saver_enabled);
1493 this.binding.osOptimizationBody.setText(
1494 getString(R.string.data_saver_enabled_explained, getString(R.string.app_name)));
1495 this.binding.osOptimizationDisable.setText(R.string.allow);
1496 this.binding.osOptimizationDisable.setOnClickListener(
1497 v -> {
1498 Intent intent =
1499 new Intent(
1500 Settings
1501 .ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS);
1502 Uri uri = Uri.parse("package:" + getPackageName());
1503 intent.setData(uri);
1504 try {
1505 startActivityForResult(intent, REQUEST_DATA_SAVER);
1506 } catch (ActivityNotFoundException e) {
1507 Toast.makeText(
1508 EditAccountActivity.this,
1509 getString(
1510 R.string.device_does_not_support_data_saver,
1511 getString(R.string.app_name)),
1512 Toast.LENGTH_SHORT)
1513 .show();
1514 }
1515 });
1516 } else if (showBatteryWarning) {
1517 this.binding.osOptimizationDisable.setText(R.string.disable);
1518 this.binding.osOptimizationHeadline.setText(R.string.battery_optimizations_enabled);
1519 this.binding.osOptimizationBody.setText(
1520 getString(
1521 R.string.battery_optimizations_enabled_explained,
1522 getString(R.string.app_name)));
1523 this.binding.osOptimizationDisable.setOnClickListener(
1524 v -> {
1525 Intent intent =
1526 new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
1527 Uri uri = Uri.parse("package:" + getPackageName());
1528 intent.setData(uri);
1529 try {
1530 startActivityForResult(intent, REQUEST_BATTERY_OP);
1531 } catch (ActivityNotFoundException e) {
1532 Toast.makeText(
1533 EditAccountActivity.this,
1534 R.string.device_does_not_support_battery_op,
1535 Toast.LENGTH_SHORT)
1536 .show();
1537 }
1538 });
1539 }
1540 }
1541
1542 public void showWipePepDialog() {
1543 final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
1544 builder.setTitle(getString(R.string.clear_other_devices));
1545 builder.setIconAttribute(android.R.attr.alertDialogIcon);
1546 builder.setMessage(getString(R.string.clear_other_devices_desc));
1547 builder.setNegativeButton(getString(R.string.cancel), null);
1548 builder.setPositiveButton(
1549 getString(R.string.accept),
1550 (dialog, which) -> mAccount.getAxolotlService().wipeOtherPepDevices());
1551 builder.create().show();
1552 }
1553
1554 private void editMamPrefs() {
1555 this.mFetchingMamPrefsToast =
1556 Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG);
1557 this.mFetchingMamPrefsToast.show();
1558 xmppConnectionService.fetchMamPreferences(mAccount, this);
1559 }
1560
1561 @Override
1562 public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
1563 refreshUi();
1564 }
1565
1566 @Override
1567 public void onCaptchaRequested(
1568 final Account account, final String id, final Data data, final Bitmap captcha) {
1569 runOnUiThread(
1570 () -> {
1571 if (mCaptchaDialog != null && mCaptchaDialog.isShowing()) {
1572 mCaptchaDialog.dismiss();
1573 }
1574 if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
1575 Log.d(Config.LOGTAG, "activity not running when captcha was requested");
1576 return;
1577 }
1578 final MaterialAlertDialogBuilder builder =
1579 new MaterialAlertDialogBuilder(EditAccountActivity.this);
1580 final View view = getLayoutInflater().inflate(R.layout.captcha, null);
1581 final ImageView imageView = view.findViewById(R.id.captcha);
1582 final EditText input = view.findViewById(R.id.input);
1583 imageView.setImageBitmap(captcha);
1584
1585 builder.setTitle(getString(R.string.captcha_required));
1586 builder.setView(view);
1587
1588 builder.setPositiveButton(
1589 getString(R.string.ok),
1590 (dialog, which) -> {
1591 String rc = input.getText().toString();
1592 data.put("username", account.getUsername());
1593 data.put("password", account.getPassword());
1594 data.put("ocr", rc);
1595 data.submit();
1596
1597 if (xmppConnectionServiceBound) {
1598 xmppConnectionService.sendCreateAccountWithCaptchaPacket(
1599 account, id, data);
1600 }
1601 });
1602 builder.setNegativeButton(
1603 getString(R.string.cancel),
1604 (dialog, which) -> {
1605 if (xmppConnectionService != null) {
1606 xmppConnectionService.sendCreateAccountWithCaptchaPacket(
1607 account, null, null);
1608 }
1609 });
1610
1611 builder.setOnCancelListener(
1612 dialog -> {
1613 if (xmppConnectionService != null) {
1614 xmppConnectionService.sendCreateAccountWithCaptchaPacket(
1615 account, null, null);
1616 }
1617 });
1618 mCaptchaDialog = builder.create();
1619 mCaptchaDialog.show();
1620 input.requestFocus();
1621 });
1622 }
1623
1624 public void onShowErrorToast(final int resId) {
1625 runOnUiThread(
1626 () -> Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show());
1627 }
1628
1629 @Override
1630 public void onPreferencesFetched(final Element prefs) {
1631 runOnUiThread(
1632 () -> {
1633 if (mFetchingMamPrefsToast != null) {
1634 mFetchingMamPrefsToast.cancel();
1635 }
1636 final MaterialAlertDialogBuilder builder =
1637 new MaterialAlertDialogBuilder(EditAccountActivity.this);
1638 builder.setTitle(R.string.server_side_mam_prefs);
1639 String defaultAttr = prefs.getAttribute("default");
1640 final List<String> defaults = Arrays.asList("never", "roster", "always");
1641 final AtomicInteger choice =
1642 new AtomicInteger(Math.max(0, defaults.indexOf(defaultAttr)));
1643 builder.setSingleChoiceItems(
1644 R.array.mam_prefs, choice.get(), (dialog, which) -> choice.set(which));
1645 builder.setNegativeButton(R.string.cancel, null);
1646 builder.setPositiveButton(
1647 R.string.ok,
1648 (dialog, which) -> {
1649 prefs.setAttribute("default", defaults.get(choice.get()));
1650 xmppConnectionService.pushMamPreferences(mAccount, prefs);
1651 });
1652 if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
1653 builder.create().show();
1654 }
1655 });
1656 }
1657
1658 @Override
1659 public void onPreferencesFetchFailed() {
1660 runOnUiThread(
1661 () -> {
1662 if (mFetchingMamPrefsToast != null) {
1663 mFetchingMamPrefsToast.cancel();
1664 }
1665 Toast.makeText(
1666 EditAccountActivity.this,
1667 R.string.unable_to_fetch_mam_prefs,
1668 Toast.LENGTH_LONG)
1669 .show();
1670 });
1671 }
1672
1673 @Override
1674 public void OnUpdateBlocklist(Status status) {
1675 refreshUi();
1676 }
1677}