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