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