1package eu.siacs.conversations.ui;
2
3import android.app.Activity;
4import android.app.KeyguardManager;
5import android.app.PendingIntent;
6import android.content.ActivityNotFoundException;
7import android.content.Context;
8import android.content.Intent;
9import android.content.IntentSender;
10import android.content.SharedPreferences;
11import android.graphics.Bitmap;
12import android.graphics.drawable.ColorDrawable;
13import android.net.Uri;
14import android.os.Bundle;
15import android.os.Build;
16import android.os.Handler;
17import android.preference.PreferenceManager;
18import android.provider.Settings;
19import android.security.KeyChain;
20import android.security.KeyChainAliasCallback;
21import android.text.Editable;
22import android.text.SpannableString;
23import android.text.TextUtils;
24import android.text.TextWatcher;
25import android.text.format.DateUtils;
26import android.text.method.LinkMovementMethod;
27import android.util.Log;
28import android.view.Menu;
29import android.view.MenuItem;
30import android.view.View;
31import android.view.View.OnClickListener;
32import android.widget.CheckBox;
33import android.widget.CompoundButton.OnCheckedChangeListener;
34import android.widget.EditText;
35import android.widget.ImageView;
36import android.widget.TextView;
37import android.widget.Toast;
38import androidx.annotation.NonNull;
39import androidx.appcompat.app.ActionBar;
40import androidx.appcompat.app.AlertDialog;
41import androidx.databinding.DataBindingUtil;
42import androidx.lifecycle.Lifecycle;
43import com.google.android.material.color.MaterialColors;
44import com.google.android.material.dialog.MaterialAlertDialogBuilder;
45import com.google.android.material.textfield.TextInputLayout;
46import com.google.common.base.CharMatcher;
47import com.google.common.base.Strings;
48
49import com.rarepebble.colorpicker.ColorPickerView;
50
51import org.openintents.openpgp.util.OpenPgpUtils;
52
53import java.text.DateFormat;
54import java.util.Arrays;
55import java.util.Calendar;
56import java.util.Date;
57import java.util.List;
58import java.util.Set;
59import java.util.concurrent.atomic.AtomicInteger;
60
61import de.gultsch.common.Linkify;
62import eu.siacs.conversations.AppSettings;
63import eu.siacs.conversations.Config;
64import eu.siacs.conversations.R;
65import eu.siacs.conversations.crypto.axolotl.AxolotlService;
66import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
67import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
68import eu.siacs.conversations.databinding.ActivityEditAccountBinding;
69import eu.siacs.conversations.databinding.DialogPresenceBinding;
70import eu.siacs.conversations.entities.Account;
71import eu.siacs.conversations.entities.PresenceTemplate;
72import eu.siacs.conversations.services.BarcodeProvider;
73import eu.siacs.conversations.services.QuickConversationsService;
74import eu.siacs.conversations.services.XmppConnectionService;
75import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
76import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested;
77import eu.siacs.conversations.ui.TimePreference;
78import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
79import eu.siacs.conversations.ui.adapter.PresenceTemplateAdapter;
80import eu.siacs.conversations.ui.text.FixedURLSpan;
81import eu.siacs.conversations.ui.util.AvatarWorkerTask;
82import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
83import eu.siacs.conversations.ui.util.PendingItem;
84import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
85import eu.siacs.conversations.utils.Compatibility;
86import eu.siacs.conversations.utils.CryptoHelper;
87import eu.siacs.conversations.utils.Resolver;
88import eu.siacs.conversations.utils.SignupUtils;
89import eu.siacs.conversations.utils.TorServiceUtils;
90import eu.siacs.conversations.utils.UIHelper;
91import eu.siacs.conversations.utils.XmppUri;
92import eu.siacs.conversations.xml.Element;
93import eu.siacs.conversations.xmpp.Jid;
94import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
95import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
96import eu.siacs.conversations.xmpp.XmppConnection;
97import eu.siacs.conversations.xmpp.XmppConnection.Features;
98import eu.siacs.conversations.xmpp.forms.Data;
99import eu.siacs.conversations.xmpp.pep.Avatar;
100
101import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
102import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
103
104import im.conversations.android.xmpp.model.stanza.Presence;
105import java.util.Arrays;
106import java.util.List;
107import java.util.Set;
108import java.util.concurrent.atomic.AtomicInteger;
109import okhttp3.HttpUrl;
110import okhttp3.HttpUrl;
111import org.openintents.openpgp.util.OpenPgpUtils;
112import org.openintents.openpgp.util.OpenPgpUtils;
113
114public class EditAccountActivity extends OmemoActivity
115 implements OnAccountUpdate,
116 OnUpdateBlocklist,
117 OnKeyStatusUpdated,
118 OnCaptchaRequested,
119 KeyChainAliasCallback,
120 XmppConnectionService.OnShowErrorToast,
121 XmppConnectionService.OnMamPreferencesFetched {
122
123 public static final String EXTRA_OPENED_FROM_NOTIFICATION = "opened_from_notification";
124 public static final String EXTRA_FORCE_REGISTER = "force_register";
125
126 private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
127 private static final int REQUEST_DATA_SAVER = 0xf244;
128 private static final int REQUEST_CHANGE_STATUS = 0xee11;
129 private static final int REQUEST_ORBOT = 0xff22;
130 private static final int REQUEST_UNLOCK = 0xff23;
131 private final PendingItem<PresenceTemplate> mPendingPresenceTemplate = new PendingItem<>();
132 private AlertDialog mCaptchaDialog = null;
133 private Jid jidToEdit;
134 private boolean mInitMode = false;
135 private Boolean mForceRegister = null;
136 private boolean mUsernameMode = false;
137 private boolean mShowOptions = false;
138 private Account mAccount;
139 private final OnClickListener mCancelButtonClickListener =
140 v -> {
141 deleteAccountAndReturnIfNecessary();
142 finish();
143 };
144 private final UiCallback<Avatar> mAvatarFetchCallback =
145 new UiCallback<Avatar>() {
146
147 @Override
148 public void userInputRequired(final PendingIntent pi, final Avatar avatar) {
149 finishInitialSetup(avatar);
150 }
151
152 @Override
153 public void success(final Avatar avatar) {
154 finishInitialSetup(avatar);
155 }
156
157 @Override
158 public void error(final int errorCode, final Avatar avatar) {
159 finishInitialSetup(avatar);
160 }
161 };
162 private final OnClickListener mAvatarClickListener =
163 new OnClickListener() {
164 @Override
165 public void onClick(final View view) {
166 if (mAccount != null) {
167 final Intent intent =
168 new Intent(
169 getApplicationContext(),
170 PublishProfilePictureActivity.class);
171 intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString());
172 startActivity(intent);
173 }
174 }
175 };
176 private String messageFingerprint;
177 private boolean mFetchingAvatar = false;
178 private Toast mFetchingMamPrefsToast;
179 private String mSavedInstanceAccount;
180 private boolean mSavedInstanceInit = false;
181 private XmppUri pendingUri = null;
182 private boolean mUseTor;
183 private ActivityEditAccountBinding binding;
184 private final OnClickListener mSaveButtonClickListener =
185 new OnClickListener() {
186
187 @Override
188 public void onClick(final View v) {
189 final String password = binding.accountPassword.getText().toString();
190 final boolean wasDisabled =
191 mAccount != null && mAccount.getStatus() == Account.State.DISABLED;
192 final boolean accountInfoEdited = accountInfoEdited();
193
194 ColorDrawable previewColor = (ColorDrawable) binding.colorPreview.getBackground();
195 if (previewColor != null && previewColor.getColor() != mAccount.getColor(isDark())) {
196 mAccount.setColor(previewColor.getColor());
197 }
198
199 if (mInitMode && mAccount != null) {
200 mAccount.setOption(Account.OPTION_DISABLED, false);
201 }
202 if (mAccount != null
203 && Arrays.asList(Account.State.DISABLED, Account.State.LOGGED_OUT)
204 .contains(mAccount.getStatus())
205 && !accountInfoEdited) {
206 mAccount.setOption(Account.OPTION_SOFT_DISABLED, false);
207 mAccount.setOption(Account.OPTION_DISABLED, false);
208 if (!xmppConnectionService.updateAccount(mAccount)) {
209 Toast.makeText(
210 EditAccountActivity.this,
211 R.string.unable_to_update_account,
212 Toast.LENGTH_SHORT)
213 .show();
214 }
215 return;
216 }
217 final boolean registerNewAccount;
218 if (mForceRegister != null) {
219 registerNewAccount = mForceRegister;
220 } else {
221 registerNewAccount =
222 binding.accountRegisterNew.isChecked()
223 && !Config.DISALLOW_REGISTRATION_IN_UI;
224 }
225 if (mUsernameMode && binding.accountJid.getText().toString().contains("@")) {
226 binding.accountJidLayout.setError(getString(R.string.invalid_username));
227 removeErrorsOnAllBut(binding.accountJidLayout);
228 binding.accountJid.requestFocus();
229 return;
230 }
231
232 XmppConnection connection =
233 mAccount == null ? null : mAccount.getXmppConnection();
234 final boolean startOrbot =
235 mAccount != null
236 && mAccount.getStatus() == Account.State.TOR_NOT_AVAILABLE;
237 if (startOrbot) {
238 if (TorServiceUtils.isOrbotInstalled(EditAccountActivity.this)) {
239 TorServiceUtils.startOrbot(EditAccountActivity.this, REQUEST_ORBOT);
240 } else {
241 TorServiceUtils.downloadOrbot(EditAccountActivity.this, REQUEST_ORBOT);
242 }
243 return;
244 }
245
246 if (inNeedOfSaslAccept()) {
247 mAccount.resetPinnedMechanism();
248 if (!xmppConnectionService.updateAccount(mAccount)) {
249 Toast.makeText(
250 EditAccountActivity.this,
251 R.string.unable_to_update_account,
252 Toast.LENGTH_SHORT)
253 .show();
254 }
255 return;
256 }
257
258 final boolean openRegistrationUrl =
259 registerNewAccount
260 && !accountInfoEdited
261 && mAccount != null
262 && mAccount.getStatus() == Account.State.REGISTRATION_WEB;
263 final boolean openPaymentUrl =
264 mAccount != null
265 && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED;
266 final boolean redirectionWorthyStatus = openPaymentUrl || openRegistrationUrl;
267 final HttpUrl url =
268 connection != null && redirectionWorthyStatus
269 ? connection.getRedirectionUrl()
270 : null;
271 if (url != null && !wasDisabled) {
272 try {
273 startActivity(
274 new Intent(Intent.ACTION_VIEW, Uri.parse(url.toString())));
275 return;
276 } catch (ActivityNotFoundException e) {
277 Toast.makeText(
278 EditAccountActivity.this,
279 R.string.application_found_to_open_website,
280 Toast.LENGTH_SHORT)
281 .show();
282 return;
283 }
284 }
285
286 final Jid jid;
287 try {
288 if (mUsernameMode) {
289 jid =
290 Jid.of(
291 binding.accountJid.getText().toString(),
292 getUserModeDomain(),
293 null);
294 } else {
295 jid = Jid.ofUserInput(binding.accountJid.getText().toString());
296 Resolver.checkDomain(jid);
297 }
298 } catch (final NullPointerException | IllegalArgumentException e) {
299 if (mUsernameMode) {
300 binding.accountJidLayout.setError(getString(R.string.invalid_username));
301 } else {
302 binding.accountJidLayout.setError(getString(R.string.invalid_jid));
303 }
304 binding.accountJid.requestFocus();
305 removeErrorsOnAllBut(binding.accountJidLayout);
306 return;
307 }
308 final String hostname;
309 int numericPort = 5222;
310 if (mShowOptions) {
311 hostname = CharMatcher.whitespace().removeFrom(binding.hostname.getText());
312 final String port =
313 CharMatcher.whitespace().removeFrom(binding.port.getText());
314 if (Resolver.invalidHostname(hostname)) {
315 binding.hostnameLayout.setError(getString(R.string.not_valid_hostname));
316 binding.hostname.requestFocus();
317 removeErrorsOnAllBut(binding.hostnameLayout);
318 return;
319 }
320 if (!hostname.isEmpty()) {
321 try {
322 numericPort = Integer.parseInt(port);
323 if (numericPort < 0 || numericPort > 65535) {
324 binding.portLayout.setError(
325 getString(R.string.not_a_valid_port));
326 removeErrorsOnAllBut(binding.portLayout);
327 binding.port.requestFocus();
328 return;
329 }
330
331 } catch (NumberFormatException e) {
332 binding.portLayout.setError(getString(R.string.not_a_valid_port));
333 removeErrorsOnAllBut(binding.portLayout);
334 binding.port.requestFocus();
335 return;
336 }
337 }
338 } else {
339 hostname = null;
340 }
341
342 if (jid.getLocal() == null) {
343 if (mUsernameMode) {
344 binding.accountJidLayout.setError(getString(R.string.invalid_username));
345 } else {
346 binding.accountJidLayout.setError(getString(R.string.invalid_jid));
347 }
348 removeErrorsOnAllBut(binding.accountJidLayout);
349 binding.accountJid.requestFocus();
350 return;
351 }
352 if (mAccount != null) {
353 if (mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
354 mAccount.setOption(
355 Account.OPTION_MAGIC_CREATE,
356 mAccount.getPassword().contains(password));
357 }
358 mAccount.setJid(jid);
359 mAccount.setPort(numericPort);
360 mAccount.setHostname(hostname);
361 binding.accountJidLayout.setError(null);
362 mAccount.setPassword(password);
363 mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
364 if (!xmppConnectionService.updateAccount(mAccount)) {
365 Toast.makeText(
366 EditAccountActivity.this,
367 R.string.unable_to_update_account,
368 Toast.LENGTH_SHORT)
369 .show();
370 return;
371 }
372 } else {
373 if (xmppConnectionService.findAccountByJid(jid) != null) {
374 binding.accountJidLayout.setError(
375 getString(R.string.account_already_exists));
376 removeErrorsOnAllBut(binding.accountJidLayout);
377 binding.accountJid.requestFocus();
378 return;
379 }
380 mAccount = new Account(jid.asBareJid(), password);
381 mAccount.setPort(numericPort);
382 mAccount.setHostname(hostname);
383 mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
384 xmppConnectionService.createAccount(mAccount);
385 }
386 binding.hostnameLayout.setError(null);
387 binding.portLayout.setError(null);
388 if (mAccount.isOnion()) {
389 Toast.makeText(
390 EditAccountActivity.this,
391 R.string.audio_video_disabled_tor,
392 Toast.LENGTH_LONG)
393 .show();
394 }
395 if (mAccount.isEnabled() && !registerNewAccount && !mInitMode) {
396 finish();
397 } else {
398 updateSaveButton();
399 updateAccountInformation(true);
400 }
401 }
402 };
403 private final TextWatcher mTextWatcher =
404 new TextWatcher() {
405
406 @Override
407 public void onTextChanged(
408 final CharSequence s, final int start, final int before, final int count) {
409 updatePortLayout();
410 updateSaveButton();
411 }
412
413 @Override
414 public void beforeTextChanged(
415 final CharSequence s, final int start, final int count, final int after) {}
416
417 @Override
418 public void afterTextChanged(final Editable s) {}
419 };
420 private final View.OnFocusChangeListener mEditTextFocusListener =
421 new View.OnFocusChangeListener() {
422 @Override
423 public void onFocusChange(View view, boolean b) {
424 EditText et = (EditText) view;
425 if (b) {
426 int resId =
427 mUsernameMode
428 ? R.string.username
429 : R.string.account_settings_example_jabber_id;
430 if (view.getId() == R.id.hostname) {
431 resId =
432 mUseTor
433 ? R.string.hostname_or_onion
434 : R.string.hostname_example;
435 }
436 final int res = resId;
437 new Handler().postDelayed(() -> et.setHint(res), 200);
438 } else {
439 et.setHint(null);
440 }
441 }
442 };
443
444 private static void setAvailabilityRadioButton(
445 Presence.Availability status, DialogPresenceBinding binding) {
446 if (status == null) {
447 binding.online.setChecked(true);
448 return;
449 }
450 switch (status) {
451 case DND:
452 binding.dnd.setChecked(true);
453 break;
454 case XA:
455 binding.xa.setChecked(true);
456 break;
457 case AWAY:
458 binding.away.setChecked(true);
459 break;
460 default:
461 binding.online.setChecked(true);
462 }
463 }
464
465 private static Presence.Availability getAvailabilityRadioButton(DialogPresenceBinding binding) {
466 if (binding.dnd.isChecked()) {
467 return Presence.Availability.DND;
468 } else if (binding.xa.isChecked()) {
469 return Presence.Availability.XA;
470 } else if (binding.away.isChecked()) {
471 return Presence.Availability.AWAY;
472 } else {
473 return Presence.Availability.ONLINE;
474 }
475 }
476
477 public void refreshUiReal() {
478 invalidateOptionsMenu();
479 if (mAccount != null && mAccount.getStatus() != Account.State.ONLINE && mFetchingAvatar) {
480 Intent intent = new Intent(this, StartConversationActivity.class);
481 StartConversationActivity.addInviteUri(intent, getIntent());
482 startActivity(intent);
483 finish();
484 } else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) {
485 if (!mFetchingAvatar) {
486 mFetchingAvatar = true;
487 xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback);
488 }
489 }
490 if (mAccount != null) {
491 updateAccountInformation(false);
492 }
493 updateSaveButton();
494 }
495
496 @Override
497 public boolean onNavigateUp() {
498 deleteAccountAndReturnIfNecessary();
499 return super.onNavigateUp();
500 }
501
502 @Override
503 public void onBackPressed() {
504 deleteAccountAndReturnIfNecessary();
505 super.onBackPressed();
506 }
507
508 private void deleteAccountAndReturnIfNecessary() {
509 if (mInitMode
510 && mAccount != null
511 && !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)) {
512 xmppConnectionService.deleteAccount(mAccount);
513 }
514
515 final boolean magicCreate =
516 mAccount != null
517 && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
518 && !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY);
519 final Jid jid = mAccount == null ? null : mAccount.getJid();
520
521 if (SignupUtils.isSupportTokenRegistry()
522 && jid != null
523 && magicCreate
524 && !jid.getDomain().equals(Config.MAGIC_CREATE_DOMAIN)) {
525 final Jid preset;
526 if (mAccount.isOptionSet(Account.OPTION_FIXED_USERNAME)) {
527 preset = jid.asBareJid();
528 } else {
529 preset = jid.getDomain();
530 }
531 final Intent intent =
532 SignupUtils.getTokenRegistrationIntent(
533 this, preset, mAccount.getKey(Account.KEY_PRE_AUTH_REGISTRATION_TOKEN));
534 StartConversationActivity.addInviteUri(intent, getIntent());
535 startActivity(intent);
536 return;
537 }
538
539 final List<Account> accounts =
540 xmppConnectionService == null ? null : xmppConnectionService.getAccounts();
541 if (accounts != null && accounts.isEmpty() && Config.MAGIC_CREATE_DOMAIN != null) {
542 Intent intent =
543 SignupUtils.getSignUpIntent(this, mForceRegister != null && mForceRegister);
544 StartConversationActivity.addInviteUri(intent, getIntent());
545 startActivity(intent);
546 }
547 }
548
549 @Override
550 public void onAccountUpdate() {
551 refreshUi();
552 }
553
554 protected void finishInitialSetup(final Avatar avatar) {
555 runOnUiThread(
556 () -> {
557 SoftKeyboardUtils.hideSoftKeyboard(EditAccountActivity.this);
558 final Intent intent;
559 final XmppConnection connection = mAccount.getXmppConnection();
560 final boolean wasFirstAccount =
561 xmppConnectionService != null
562 && xmppConnectionService.getAccounts().size() == 1;
563 if (avatar != null || (connection != null && !connection.getFeatures().pep())) {
564 intent =
565 new Intent(
566 getApplicationContext(), StartConversationActivity.class);
567 intent.putExtra("init", true);
568 intent.putExtra(
569 EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString());
570 } else {
571 intent =
572 new Intent(
573 getApplicationContext(),
574 PublishProfilePictureActivity.class);
575 intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString());
576 intent.putExtra("setup", true);
577 }
578 if (wasFirstAccount) {
579 intent.setFlags(
580 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
581 }
582 StartConversationActivity.addInviteUri(intent, getIntent());
583 startActivity(intent);
584 finish();
585 });
586 }
587
588 @Override
589 public void onActivityResult(int requestCode, int resultCode, Intent data) {
590 // TODO check for Camera / Scan permission
591 super.onActivityResult(requestCode, resultCode, data);
592 if (requestCode == REQUEST_BATTERY_OP || requestCode == REQUEST_DATA_SAVER) {
593 updateAccountInformation(mAccount == null);
594 }
595 if (requestCode == REQUEST_BATTERY_OP) {
596 // the result code is always 0 even when battery permission were granted
597 XmppConnectionService.toggleForegroundService(xmppConnectionService);
598 }
599 if (requestCode == REQUEST_CHANGE_STATUS) {
600 PresenceTemplate template = mPendingPresenceTemplate.pop();
601 if (template != null && resultCode == Activity.RESULT_OK) {
602 generateSignature(data, template);
603 } else {
604 Log.d(Config.LOGTAG, "pgp result not ok");
605 }
606 }
607 if (requestCode == REQUEST_UNLOCK) {
608 if (resultCode == RESULT_OK) {
609 openChangePassword(true);
610 }
611 }
612 }
613
614 @Override
615 protected void processFingerprintVerification(XmppUri uri) {
616 processFingerprintVerification(uri, true);
617 }
618
619 protected void processFingerprintVerification(XmppUri uri, boolean showWarningToast) {
620 if (mAccount != null
621 && mAccount.getJid().asBareJid().equals(uri.getJid())
622 && uri.hasFingerprints()) {
623 if (xmppConnectionService.verifyFingerprints(mAccount, uri.getFingerprints())) {
624 Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
625 updateAccountInformation(false);
626 }
627 } else if (showWarningToast) {
628 Toast.makeText(this, R.string.invalid_barcode, Toast.LENGTH_SHORT).show();
629 }
630 }
631
632 private void updatePortLayout() {
633 final String hostname = this.binding.hostname.getText().toString();
634 if (TextUtils.isEmpty(hostname)) {
635 this.binding.portLayout.setEnabled(false);
636 this.binding.portLayout.setError(null);
637 } else {
638 this.binding.portLayout.setEnabled(true);
639 }
640 }
641
642 protected void updateSaveButton() {
643 boolean accountInfoEdited = accountInfoEdited();
644
645 if (accountInfoEdited && !mInitMode) {
646 this.binding.saveButton.setText(R.string.save);
647 this.binding.saveButton.setEnabled(true);
648 } else if (mAccount != null
649 && (mAccount.getStatus() == Account.State.CONNECTING
650 || mAccount.getStatus() == Account.State.REGISTRATION_SUCCESSFUL
651 || mFetchingAvatar)) {
652 this.binding.saveButton.setEnabled(false);
653 this.binding.saveButton.setText(R.string.account_status_connecting);
654 } else if (mAccount != null
655 && mAccount.getStatus() == Account.State.DISABLED
656 && !mInitMode) {
657 this.binding.saveButton.setEnabled(true);
658 this.binding.saveButton.setText(R.string.enable);
659 } else if (torNeedsInstall(mAccount)) {
660 this.binding.saveButton.setEnabled(true);
661 this.binding.saveButton.setText(R.string.install_orbot);
662 } else if (torNeedsStart(mAccount)) {
663 this.binding.saveButton.setEnabled(true);
664 this.binding.saveButton.setText(R.string.start_orbot);
665 } else {
666 this.binding.saveButton.setEnabled(true);
667 if (!mInitMode) {
668 if (mAccount != null && mAccount.isOnlineAndConnected()) {
669 this.binding.saveButton.setText(R.string.save);
670 if (!accountInfoEdited) {
671 this.binding.saveButton.setEnabled(false);
672 }
673 } else {
674 XmppConnection connection =
675 mAccount == null ? null : mAccount.getXmppConnection();
676 HttpUrl url =
677 connection != null
678 && mAccount.getStatus()
679 == Account.State.PAYMENT_REQUIRED
680 ? connection.getRedirectionUrl()
681 : null;
682 if (url != null) {
683 this.binding.saveButton.setText(R.string.open_website);
684 } else if (inNeedOfSaslAccept()) {
685 this.binding.saveButton.setText(R.string.accept);
686 } else {
687 this.binding.saveButton.setText(R.string.connect);
688 }
689 }
690 } else {
691 XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
692 HttpUrl url =
693 connection != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB
694 ? connection.getRedirectionUrl()
695 : null;
696 if (url != null
697 && this.binding.accountRegisterNew.isChecked()
698 && !accountInfoEdited) {
699 this.binding.saveButton.setText(R.string.open_website);
700 } else {
701 this.binding.saveButton.setText(R.string.next);
702 }
703 }
704 }
705 }
706
707 private boolean torNeedsInstall(final Account account) {
708 return account != null
709 && account.getStatus() == Account.State.TOR_NOT_AVAILABLE
710 && !TorServiceUtils.isOrbotInstalled(this);
711 }
712
713 private boolean torNeedsStart(final Account account) {
714 return account != null && account.getStatus() == Account.State.TOR_NOT_AVAILABLE;
715 }
716
717 protected boolean accountInfoEdited() {
718 if (this.mAccount == null) {
719 return false;
720 }
721 ColorDrawable previewColor = (ColorDrawable) binding.colorPreview.getBackground();
722 return jidEdited()
723 || !this.mAccount
724 .getPassword()
725 .equals(this.binding.accountPassword.getText().toString())
726 || !this.mAccount.getHostname().equals(this.binding.hostname.getText().toString())
727 || this.mAccount.getColor(isDark()) != (previewColor == null ? 0 : previewColor.getColor())
728 || !String.valueOf(this.mAccount.getPort())
729 .equals(this.binding.port.getText().toString());
730 }
731
732 protected boolean jidEdited() {
733 final String unmodified;
734 if (mUsernameMode) {
735 unmodified = this.mAccount.getJid().getLocal();
736 } else {
737 unmodified = this.mAccount.getJid().asBareJid().toString();
738 }
739 return !unmodified.equals(this.binding.accountJid.getText().toString());
740 }
741
742 @Override
743 protected String getShareableUri(boolean http) {
744 if (mAccount != null) {
745 return http ? mAccount.getShareableLink() : mAccount.getShareableUri();
746 } else {
747 return null;
748 }
749 }
750
751 @Override
752 protected void onCreate(final Bundle savedInstanceState) {
753 super.onCreate(savedInstanceState);
754 if (savedInstanceState != null) {
755 this.mSavedInstanceAccount = savedInstanceState.getString("account");
756 this.mSavedInstanceInit = savedInstanceState.getBoolean("initMode", false);
757 }
758 this.binding = DataBindingUtil.setContentView(this, R.layout.activity_edit_account);
759 Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
760 setSupportActionBar(binding.toolbar);
761 binding.accountJid.addTextChangedListener(this.mTextWatcher);
762 binding.accountJid.setOnFocusChangeListener(this.mEditTextFocusListener);
763 this.binding.accountPassword.addTextChangedListener(this.mTextWatcher);
764
765 this.binding.avater.setOnClickListener(this.mAvatarClickListener);
766 this.binding.hostname.addTextChangedListener(mTextWatcher);
767 this.binding.hostname.setOnFocusChangeListener(mEditTextFocusListener);
768 this.binding.clearDevices.setOnClickListener(v -> showWipePepDialog());
769 this.binding.port.setText(String.valueOf(Resolver.XMPP_PORT_STARTTLS));
770 this.binding.port.addTextChangedListener(mTextWatcher);
771 this.binding.saveButton.setOnClickListener(this.mSaveButtonClickListener);
772 this.binding.cancelButton.setOnClickListener(this.mCancelButtonClickListener);
773 if (savedInstanceState != null && savedInstanceState.getBoolean("showMoreTable")) {
774 changeMoreTableVisibility(true);
775 }
776 final OnCheckedChangeListener OnCheckedShowConfirmPassword =
777 (buttonView, isChecked) -> updateSaveButton();
778 this.binding.accountRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword);
779 if (Config.DISALLOW_REGISTRATION_IN_UI) {
780 this.binding.accountRegisterNew.setVisibility(View.GONE);
781 }
782 this.binding.actionEditYourName.setOnClickListener(this::onEditYourNameClicked);
783 binding.accountColorBox.setOnClickListener((v) -> {
784 showColorDialog();
785 });
786
787 final var preferences = getPreferences();
788 binding.quietHoursEnable.setOnClickListener((v) -> {
789 preferences.edit().putBoolean("enable_quiet_hours:" + mAccount.getUuid(), binding.quietHoursEnable.isChecked()).apply();
790 updateAccountInformation(false);
791 });
792 binding.quietHoursStartBox.setOnClickListener((v) -> {
793 final var picker = new com.google.android.material.timepicker.MaterialTimePicker.Builder()
794 .setTitleText("Quiet Hours Start")
795 .setHour((int)(preferences.getLong("quiet_hours_start:" + mAccount.getUuid(), 1320) / 60))
796 .setMinute((int)(preferences.getLong("quiet_hours_start:" + mAccount.getUuid(), 1320) % 60))
797 .build();
798 picker.addOnPositiveButtonClickListener((v2) -> {
799 preferences.edit().putLong("quiet_hours_start:" + mAccount.getUuid(), (picker.getHour() * 60) + picker.getMinute()).apply();
800 updateAccountInformation(false);
801 });
802 picker.show(getSupportFragmentManager(), "quiethoursstart");
803 });
804 binding.quietHoursEndBox.setOnClickListener((v) -> {
805 final var picker = new com.google.android.material.timepicker.MaterialTimePicker.Builder()
806 .setTitleText("Quiet Hours End")
807 .setHour((int)(preferences.getLong("quiet_hours_end:" + mAccount.getUuid(), 1320) / 60))
808 .setMinute((int)(preferences.getLong("quiet_hours_end:" + mAccount.getUuid(), 1320) % 60))
809 .build();
810 picker.addOnPositiveButtonClickListener((v2) -> {
811 preferences.edit().putLong("quiet_hours_end:" + mAccount.getUuid(), (picker.getHour() * 60) + picker.getMinute()).apply();
812 updateAccountInformation(false);
813 });
814 picker.show(getSupportFragmentManager(), "quiethoursend");
815 });
816 this.binding.scanButton.setOnClickListener((v) -> ScanActivity.scan(this));
817 }
818
819 private void onEditYourNameClicked(View view) {
820 quickEdit(
821 mAccount.getDisplayName(),
822 R.string.your_name,
823 value -> {
824 final String displayName = value.trim();
825 updateDisplayName(displayName);
826 mAccount.setDisplayName(displayName);
827 xmppConnectionService.publishDisplayName(mAccount);
828 refreshAvatar();
829 return null;
830 },
831 true);
832 }
833
834 private void refreshAvatar() {
835 AvatarWorkerTask.loadAvatar(
836 mAccount, binding.avater, R.dimen.avatar_on_details_screen_size);
837 }
838
839 @Override
840 public boolean onCreateOptionsMenu(final Menu menu) {
841 super.onCreateOptionsMenu(menu);
842 getMenuInflater().inflate(R.menu.editaccount, menu);
843 final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list);
844 final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
845 final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
846 final MenuItem deleteAccount = menu.findItem(R.id.action_delete_account);
847 final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate);
848 final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs);
849 final MenuItem changePresence = menu.findItem(R.id.action_change_presence);
850 final MenuItem share = menu.findItem(R.id.action_share);
851 final MenuItem importBackup = menu.findItem(R.id.action_import_backup);
852 renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null);
853
854 share.setVisible(mAccount != null && !mInitMode);
855 importBackup.setVisible(mAccount == null || mInitMode);
856
857 if (mAccount != null && mAccount.isOnlineAndConnected()) {
858 if (!mAccount.getXmppConnection().getFeatures().blocking()) {
859 showBlocklist.setVisible(false);
860 }
861
862 if (!mAccount.getXmppConnection().getFeatures().register()) {
863 changePassword.setVisible(false);
864 deleteAccount.setVisible(false);
865 }
866 mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam());
867 changePresence.setVisible(!mInitMode);
868 } else {
869 showBlocklist.setVisible(false);
870 showMoreInfo.setVisible(false);
871 changePassword.setVisible(false);
872 deleteAccount.setVisible(false);
873 mamPrefs.setVisible(false);
874 changePresence.setVisible(false);
875 }
876 return super.onCreateOptionsMenu(menu);
877 }
878
879 @Override
880 public boolean onPrepareOptionsMenu(Menu menu) {
881 final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
882 if (showMoreInfo.isVisible()) {
883 showMoreInfo.setChecked(binding.serverInfoMore.getVisibility() == View.VISIBLE);
884 }
885 return super.onPrepareOptionsMenu(menu);
886 }
887
888 @Override
889 public void onStart() {
890 super.onStart();
891 final Intent intent = getIntent();
892 if (intent != null) {
893 try {
894 this.jidToEdit = Jid.of(intent.getStringExtra("jid"));
895 } catch (final IllegalArgumentException | NullPointerException ignored) {
896 this.jidToEdit = null;
897 }
898 final Uri data = intent.getData();
899 final XmppUri xmppUri = data == null ? null : new XmppUri(data);
900 final boolean scanned = intent.getBooleanExtra("scanned", false);
901 if (jidToEdit != null && xmppUri != null && xmppUri.hasFingerprints()) {
902 if (scanned) {
903 if (xmppConnectionServiceBound) {
904 processFingerprintVerification(xmppUri, false);
905 } else {
906 this.pendingUri = xmppUri;
907 }
908 } else {
909 displayVerificationWarningDialog(xmppUri);
910 }
911 }
912 boolean init = intent.getBooleanExtra("init", false);
913 boolean openedFromNotification =
914 intent.getBooleanExtra(EXTRA_OPENED_FROM_NOTIFICATION, false);
915 Log.d(Config.LOGTAG, "extras " + intent.getExtras());
916 this.mForceRegister =
917 intent.hasExtra(EXTRA_FORCE_REGISTER)
918 ? intent.getBooleanExtra(EXTRA_FORCE_REGISTER, false)
919 : null;
920 Log.d(Config.LOGTAG, "force register=" + mForceRegister);
921 this.mInitMode = init || this.jidToEdit == null;
922 this.messageFingerprint = intent.getStringExtra("fingerprint");
923 if (!mInitMode) {
924 this.binding.accountRegisterNew.setVisibility(View.GONE);
925 setTitle(getString(R.string.account_details));
926 configureActionBar(getSupportActionBar(), !openedFromNotification);
927 } else {
928 this.binding.avater.setVisibility(View.GONE);
929 configureActionBar(
930 getSupportActionBar(), !(init && Config.MAGIC_CREATE_DOMAIN == null));
931 if (mForceRegister != null) {
932 if (mForceRegister) {
933 setTitle(R.string.register_new_account);
934 } else {
935 setTitle(R.string.add_existing_account);
936 }
937 } else {
938 setTitle(R.string.action_add_account);
939 }
940 }
941 }
942 SharedPreferences preferences = getPreferences();
943 mUseTor = preferences.getBoolean("use_tor", getResources().getBoolean(R.bool.use_tor));
944 this.mShowOptions = mUseTor || preferences.getBoolean("show_connection_options", getResources().getBoolean(R.bool.show_connection_options));
945 this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
946 if (mForceRegister != null) {
947 this.binding.accountRegisterNew.setVisibility(View.GONE);
948 }
949 if (intent.getBooleanExtra("snikket", false)) {
950 this.binding.accountJidLayout.setHint("Snikket Address");
951 }
952 }
953
954 private void displayVerificationWarningDialog(final XmppUri xmppUri) {
955 final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
956 builder.setTitle(R.string.verify_omemo_keys);
957 View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
958 final CheckBox isTrustedSource = view.findViewById(R.id.trusted_source);
959 TextView warning = view.findViewById(R.id.warning);
960 warning.setText(R.string.verifying_omemo_keys_trusted_source_account);
961 builder.setView(view);
962 builder.setPositiveButton(
963 R.string.continue_btn,
964 (dialog, which) -> {
965 if (isTrustedSource.isChecked()) {
966 processFingerprintVerification(xmppUri, false);
967 } else {
968 finish();
969 }
970 });
971 builder.setNegativeButton(R.string.cancel, (dialog, which) -> finish());
972 final var dialog = builder.create();
973 dialog.setCanceledOnTouchOutside(false);
974 dialog.setOnCancelListener(d -> finish());
975 dialog.show();
976 }
977
978 @Override
979 public void onNewIntent(@NonNull final Intent intent) {
980 super.onNewIntent(intent);
981 if (intent.getData() != null) {
982 final XmppUri uri = new XmppUri(intent.getData());
983 if (xmppConnectionServiceBound) {
984 processFingerprintVerification(uri, false);
985 } else {
986 this.pendingUri = uri;
987 }
988 }
989 }
990
991 @Override
992 public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
993 if (mAccount != null) {
994 savedInstanceState.putString("account", mAccount.getJid().asBareJid().toString());
995 savedInstanceState.putBoolean("initMode", mInitMode);
996 savedInstanceState.putBoolean(
997 "showMoreTable", binding.serverInfoMore.getVisibility() == View.VISIBLE);
998 }
999 super.onSaveInstanceState(savedInstanceState);
1000 }
1001
1002 protected void onBackendConnected() {
1003 boolean init = true;
1004 if (mSavedInstanceAccount != null) {
1005 try {
1006 this.mAccount =
1007 xmppConnectionService.findAccountByJid(Jid.of(mSavedInstanceAccount));
1008 this.mInitMode = mSavedInstanceInit;
1009 init = false;
1010 } catch (IllegalArgumentException e) {
1011 this.mAccount = null;
1012 }
1013
1014 } else if (this.jidToEdit != null) {
1015 this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit);
1016 }
1017
1018 if (mAccount != null) {
1019 this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER);
1020 this.mUsernameMode |=
1021 mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
1022 && mAccount.isOptionSet(Account.OPTION_REGISTER);
1023 if (mPendingFingerprintVerificationUri != null) {
1024 processFingerprintVerification(mPendingFingerprintVerificationUri, false);
1025 mPendingFingerprintVerificationUri = null;
1026 }
1027 updateAccountInformation(init);
1028 }
1029
1030 if (Config.MAGIC_CREATE_DOMAIN == null
1031 && this.xmppConnectionService.getAccounts().size() == 0) {
1032 this.binding.cancelButton.setEnabled(false);
1033 }
1034 if (mUsernameMode) {
1035 this.binding.accountJidLayout.setHint(getString(R.string.username_hint));
1036 } else {
1037 final KnownHostsAdapter mKnownHostsAdapter =
1038 new KnownHostsAdapter(
1039 this,
1040 R.layout.item_autocomplete,
1041 xmppConnectionService.getKnownHosts());
1042 this.binding.accountJid.setAdapter(mKnownHostsAdapter);
1043 }
1044
1045 if (pendingUri != null) {
1046 processFingerprintVerification(pendingUri, false);
1047 pendingUri = null;
1048 }
1049 updatePortLayout();
1050 updateSaveButton();
1051 invalidateOptionsMenu();
1052 }
1053
1054 private String getUserModeDomain() {
1055 if (mAccount != null && mAccount.getJid().getDomain() != null) {
1056 return mAccount.getServer();
1057 } else {
1058 return null;
1059 }
1060 }
1061
1062 @Override
1063 public boolean onOptionsItemSelected(final MenuItem item) {
1064 if (MenuDoubleTabUtil.shouldIgnoreTap()) {
1065 return false;
1066 }
1067 switch (item.getItemId()) {
1068 case android.R.id.home:
1069 deleteAccountAndReturnIfNecessary();
1070 break;
1071 case R.id.action_show_block_list:
1072 final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class);
1073 showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
1074 startActivity(showBlocklistIntent);
1075 break;
1076 case R.id.action_server_info_show_more:
1077 changeMoreTableVisibility(!item.isChecked());
1078 break;
1079 case R.id.action_share_barcode:
1080 shareBarcode();
1081 break;
1082 case R.id.action_share_http:
1083 shareLink(true);
1084 break;
1085 case R.id.action_share_uri:
1086 shareLink(false);
1087 break;
1088 case R.id.action_change_password_on_server:
1089 gotoChangePassword();
1090 break;
1091 case R.id.action_delete_account:
1092 deleteAccount();
1093 break;
1094 case R.id.action_mam_prefs:
1095 editMamPrefs();
1096 break;
1097 case R.id.action_renew_certificate:
1098 renewCertificate();
1099 break;
1100 case R.id.action_change_presence:
1101 changePresence();
1102 break;
1103 case R.id.action_import_backup:
1104 if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
1105 startActivity(new Intent(this, ImportBackupActivity.class));
1106 }
1107 break;
1108 }
1109 return super.onOptionsItemSelected(item);
1110 }
1111
1112 private void deleteAccount() {
1113 this.deleteAccount(mAccount, () -> finish());
1114 }
1115
1116 private boolean inNeedOfSaslAccept() {
1117 return mAccount != null
1118 && mAccount.getLastErrorStatus() == Account.State.DOWNGRADE_ATTACK
1119 && mAccount.getPinnedMechanismPriority() >= 0
1120 && !accountInfoEdited();
1121 }
1122
1123 private void shareBarcode() {
1124 Intent intent = new Intent(Intent.ACTION_SEND);
1125 intent.putExtra(Intent.EXTRA_STREAM, BarcodeProvider.getUriForAccount(this, mAccount));
1126 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1127 intent.setType("image/png");
1128 startActivity(Intent.createChooser(intent, getText(R.string.share_with)));
1129 }
1130
1131 private void changeMoreTableVisibility(final boolean visible) {
1132 binding.serverInfoMore.setVisibility(visible ? View.VISIBLE : View.GONE);
1133 binding.serverInfoLoginMechanism.setVisibility(visible ? View.VISIBLE : View.GONE);
1134 }
1135
1136 private void gotoChangePassword() {
1137 KeyguardManager keyguardManager = (KeyguardManager) this.getSystemService(Context.KEYGUARD_SERVICE);
1138 Intent credentialsIntent = keyguardManager.createConfirmDeviceCredentialIntent("Unlock required", "Please unlock in order to change your password");
1139 if (credentialsIntent == null) {
1140 openChangePassword(false);
1141 } else {
1142 startActivityForResult(credentialsIntent, REQUEST_UNLOCK);
1143 }
1144 }
1145
1146 private void openChangePassword(boolean didUnlock) {
1147 final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class);
1148 changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
1149 changePasswordIntent.putExtra("did_unlock", didUnlock);
1150 startActivity(changePasswordIntent);
1151 }
1152
1153 private void renewCertificate() {
1154 KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
1155 }
1156
1157 private void changePresence() {
1158 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1159 boolean manualStatus =
1160 sharedPreferences.getBoolean(
1161 AppSettings.MANUALLY_CHANGE_PRESENCE,
1162 getResources().getBoolean(R.bool.manually_change_presence));
1163 final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
1164 final DialogPresenceBinding binding =
1165 DataBindingUtil.inflate(getLayoutInflater(), R.layout.dialog_presence, null, false);
1166 String current = mAccount.getPresenceStatusMessage();
1167 if (current != null && !current.trim().isEmpty()) {
1168 binding.statusMessage.append(current);
1169 }
1170 setAvailabilityRadioButton(mAccount.getPresenceStatus(), binding);
1171 binding.show.setVisibility(manualStatus ? View.VISIBLE : View.GONE);
1172 List<PresenceTemplate> templates = xmppConnectionService.getPresenceTemplates(mAccount);
1173 PresenceTemplateAdapter presenceTemplateAdapter =
1174 new PresenceTemplateAdapter(this, R.layout.item_autocomplete, templates);
1175 binding.statusMessage.setAdapter(presenceTemplateAdapter);
1176 binding.statusMessage.setOnItemClickListener(
1177 (parent, view, position, id) -> {
1178 PresenceTemplate template =
1179 (PresenceTemplate) parent.getItemAtPosition(position);
1180 setAvailabilityRadioButton(template.getStatus(), binding);
1181 });
1182 builder.setTitle(R.string.edit_status_message_title);
1183 builder.setView(binding.getRoot());
1184 builder.setNegativeButton(R.string.cancel, null);
1185 builder.setPositiveButton(
1186 R.string.confirm,
1187 (dialog, which) -> {
1188 PresenceTemplate template =
1189 new PresenceTemplate(
1190 getAvailabilityRadioButton(binding),
1191 binding.statusMessage.getText().toString().trim());
1192 if (mAccount.getPgpId() != 0 && hasPgp()) {
1193 generateSignature(null, template);
1194 } else {
1195 xmppConnectionService.changeStatus(mAccount, template, null);
1196 }
1197 });
1198 builder.create().show();
1199 }
1200
1201 private void generateSignature(Intent intent, PresenceTemplate template) {
1202 xmppConnectionService
1203 .getPgpEngine()
1204 .generateSignature(
1205 intent,
1206 mAccount,
1207 template.getStatusMessage(),
1208 new UiCallback<String>() {
1209 @Override
1210 public void success(String signature) {
1211 xmppConnectionService.changeStatus(mAccount, template, signature);
1212 }
1213
1214 @Override
1215 public void error(int errorCode, String object) {}
1216
1217 @Override
1218 public void userInputRequired(PendingIntent pi, String object) {
1219 mPendingPresenceTemplate.push(template);
1220 try {
1221 startIntentSenderForResult(
1222 pi.getIntentSender(),
1223 REQUEST_CHANGE_STATUS,
1224 null,
1225 0,
1226 0,
1227 0,
1228 Compatibility.pgpStartIntentSenderOptions());
1229 } catch (final IntentSender.SendIntentException ignored) {
1230 }
1231 }
1232 });
1233 }
1234
1235 @Override
1236 public void alias(String alias) {
1237 if (alias != null) {
1238 xmppConnectionService.updateKeyInAccount(mAccount, alias);
1239 }
1240 }
1241
1242 void showColorDialog() {
1243 AlertDialog.Builder builder = new AlertDialog.Builder(this);
1244 final ColorPickerView picker = new ColorPickerView(this);
1245
1246 if (mAccount != null) picker.setColor(mAccount.getColor(isDark()));
1247 picker.showAlpha(true);
1248 picker.showHex(true);
1249 picker.showPreview(true);
1250 builder
1251 .setTitle(null)
1252 .setView(picker)
1253 .setPositiveButton(R.string.ok, (dialog, which) -> {
1254 final int color = picker.getColor();
1255 binding.colorPreview.setBackgroundColor(color);
1256 updateSaveButton();
1257 })
1258 .setNegativeButton(R.string.cancel, (dialog, which) -> {});
1259 builder.show();
1260 }
1261
1262 private void updateAccountInformation(boolean init) {
1263 if (init) {
1264 this.binding.accountJid.getEditableText().clear();
1265 if (mUsernameMode) {
1266 this.binding.accountJid.getEditableText().append(this.mAccount.getJid().getLocal());
1267 } else {
1268 this.binding
1269 .accountJid
1270 .getEditableText()
1271 .append(this.mAccount.getJid().asBareJid().toString());
1272 }
1273 this.binding.accountPassword.getEditableText().clear();
1274 this.binding.accountPassword.getEditableText().append(this.mAccount.getPassword());
1275 this.binding.hostname.setText("");
1276 this.binding.hostname.getEditableText().append(this.mAccount.getHostname());
1277 this.binding.port.setText("");
1278 this.binding.port.getEditableText().append(String.valueOf(this.mAccount.getPort()));
1279 this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
1280 }
1281
1282 final var preferences = getPreferences();
1283 binding.quietHoursEnable.setChecked(preferences.getBoolean("enable_quiet_hours:" + mAccount.getUuid(), false));
1284
1285 if (binding.quietHoursEnable.isChecked()) {
1286 binding.quietHoursStartBox.setVisibility(View.VISIBLE);
1287 binding.quietHoursEndBox.setVisibility(View.VISIBLE);
1288 } else {
1289 binding.quietHoursStartBox.setVisibility(View.GONE);
1290 binding.quietHoursEndBox.setVisibility(View.GONE);
1291 }
1292
1293 final var startTime = preferences.getLong("quiet_hours_start:" + mAccount.getUuid(), 1320);
1294 final DateFormat dateFormat = android.text.format.DateFormat.getTimeFormat(this);
1295 final Date date = TimePreference.minutesToCalender(startTime).getTime();
1296 binding.quietHoursStart.setText(dateFormat.format(date.getTime()));
1297
1298 final var endTime = preferences.getLong("quiet_hours_end:" + mAccount.getUuid(), 480);
1299 final DateFormat dateFormatE = android.text.format.DateFormat.getTimeFormat(this);
1300 final Date dateE = TimePreference.minutesToCalender(endTime).getTime();
1301 binding.quietHoursEnd.setText(dateFormat.format(dateE.getTime()));
1302
1303 if (!mInitMode && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1304 this.binding.accountPassword.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
1305 }
1306
1307 final boolean editable =
1308 !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)
1309 && !mAccount.isOptionSet(Account.OPTION_FIXED_USERNAME)
1310 && QuickConversationsService.isConversations();
1311 this.binding.accountJid.setEnabled(editable);
1312 this.binding.accountJid.setFocusable(editable);
1313 this.binding.accountJid.setFocusableInTouchMode(editable);
1314 this.binding.accountJid.setCursorVisible(editable);
1315
1316 final String displayName = mAccount.getDisplayName();
1317 updateDisplayName(displayName);
1318
1319 if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() > 1) {
1320 binding.accountColorBox.setVisibility(View.VISIBLE);
1321 binding.colorPreview.setBackgroundColor(mAccount.getColor(isDark()));
1322 binding.quietHoursBox.setVisibility(View.VISIBLE);
1323 } else {
1324 binding.accountColorBox.setVisibility(View.GONE);
1325 binding.quietHoursBox.setVisibility(View.GONE);
1326 }
1327
1328 final boolean togglePassword =
1329 mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
1330 || !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY);
1331 final boolean neverLoggedIn =
1332 !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY);
1333 final boolean editPassword = mAccount.unauthorized() || neverLoggedIn;
1334
1335 this.binding.accountPasswordLayout.setPasswordVisibilityToggleEnabled(togglePassword);
1336
1337 this.binding.accountPassword.setFocusable(editPassword);
1338 this.binding.accountPassword.setFocusableInTouchMode(editPassword);
1339 this.binding.accountPassword.setCursorVisible(editPassword);
1340 this.binding.accountPassword.setEnabled(editPassword);
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}