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