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