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