1package eu.siacs.conversations.ui;
2
3import android.app.Activity;
4import android.app.PendingIntent;
5import android.content.ActivityNotFoundException;
6import android.content.Intent;
7import android.content.IntentSender;
8import android.content.SharedPreferences;
9import android.databinding.DataBindingUtil;
10import android.graphics.Bitmap;
11import android.net.Uri;
12import android.os.Bundle;
13import android.os.Handler;
14import android.preference.PreferenceManager;
15import android.provider.Settings;
16import android.security.KeyChain;
17import android.security.KeyChainAliasCallback;
18import android.support.design.widget.TextInputLayout;
19import android.support.v7.app.ActionBar;
20import android.support.v7.app.AlertDialog;
21import android.support.v7.app.AlertDialog.Builder;
22import android.support.v7.widget.Toolbar;
23import android.text.Editable;
24import android.text.TextUtils;
25import android.text.TextWatcher;
26import android.util.Log;
27import android.view.Menu;
28import android.view.MenuItem;
29import android.view.View;
30import android.view.View.OnClickListener;
31import android.widget.CompoundButton.OnCheckedChangeListener;
32import android.widget.EditText;
33import android.widget.ImageView;
34import android.widget.Toast;
35
36import org.openintents.openpgp.util.OpenPgpUtils;
37
38import java.net.URL;
39import java.util.Arrays;
40import java.util.List;
41import java.util.Set;
42import java.util.concurrent.atomic.AtomicInteger;
43
44import eu.siacs.conversations.Config;
45import eu.siacs.conversations.R;
46import eu.siacs.conversations.crypto.axolotl.AxolotlService;
47import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
48import eu.siacs.conversations.databinding.ActivityEditAccountBinding;
49import eu.siacs.conversations.databinding.DialogPresenceBinding;
50import eu.siacs.conversations.entities.Account;
51import eu.siacs.conversations.entities.Presence;
52import eu.siacs.conversations.entities.PresenceTemplate;
53import eu.siacs.conversations.services.BarcodeProvider;
54import eu.siacs.conversations.services.QuickConversationsService;
55import eu.siacs.conversations.services.XmppConnectionService;
56import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
57import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested;
58import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
59import eu.siacs.conversations.ui.adapter.PresenceTemplateAdapter;
60import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
61import eu.siacs.conversations.ui.util.PendingItem;
62import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
63import eu.siacs.conversations.utils.CryptoHelper;
64import eu.siacs.conversations.utils.Resolver;
65import eu.siacs.conversations.utils.SignupUtils;
66import eu.siacs.conversations.utils.UIHelper;
67import eu.siacs.conversations.utils.XmppUri;
68import eu.siacs.conversations.xml.Element;
69import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
70import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
71import eu.siacs.conversations.xmpp.XmppConnection;
72import eu.siacs.conversations.xmpp.XmppConnection.Features;
73import eu.siacs.conversations.xmpp.forms.Data;
74import eu.siacs.conversations.xmpp.pep.Avatar;
75import rocks.xmpp.addr.Jid;
76
77public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, OnUpdateBlocklist,
78 OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched {
79
80 public static final String EXTRA_OPENED_FROM_NOTIFICATION = "opened_from_notification";
81
82 private static final int REQUEST_DATA_SAVER = 0xf244;
83 private static final int REQUEST_CHANGE_STATUS = 0xee11;
84 private final PendingItem<PresenceTemplate> mPendingPresenceTemplate = new PendingItem<>();
85 private AlertDialog mCaptchaDialog = null;
86 private Jid jidToEdit;
87 private boolean mInitMode = false;
88 private boolean mUsernameMode = Config.DOMAIN_LOCK != null;
89 private boolean mShowOptions = false;
90 private Account mAccount;
91 private final OnClickListener mCancelButtonClickListener = v -> {
92 deleteAccountAndReturnIfNecessary();
93 finish();
94 };
95 private final UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() {
96
97 @Override
98 public void userInputRequried(final PendingIntent pi, final Avatar avatar) {
99 finishInitialSetup(avatar);
100 }
101
102 @Override
103 public void success(final Avatar avatar) {
104 finishInitialSetup(avatar);
105 }
106
107 @Override
108 public void error(final int errorCode, final Avatar avatar) {
109 finishInitialSetup(avatar);
110 }
111 };
112 private final OnClickListener mAvatarClickListener = new OnClickListener() {
113 @Override
114 public void onClick(final View view) {
115 if (mAccount != null) {
116 final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
117 intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString());
118 startActivity(intent);
119 }
120 }
121 };
122 private String messageFingerprint;
123 private boolean mFetchingAvatar = false;
124 private Toast mFetchingMamPrefsToast;
125 private String mSavedInstanceAccount;
126 private boolean mSavedInstanceInit = false;
127 private XmppUri pendingUri = null;
128 private boolean mUseTor;
129 private ActivityEditAccountBinding binding;
130 private final OnClickListener mSaveButtonClickListener = new OnClickListener() {
131
132 @Override
133 public void onClick(final View v) {
134 final String password = binding.accountPassword.getText().toString();
135 final boolean wasDisabled = mAccount != null && mAccount.getStatus() == Account.State.DISABLED;
136 final boolean accountInfoEdited = accountInfoEdited();
137
138 if (!mInitMode && passwordChangedInMagicCreateMode()) {
139 gotoChangePassword(password);
140 return;
141 }
142 if (mInitMode && mAccount != null) {
143 mAccount.setOption(Account.OPTION_DISABLED, false);
144 }
145 if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited) {
146 mAccount.setOption(Account.OPTION_DISABLED, false);
147 if (!xmppConnectionService.updateAccount(mAccount)) {
148 Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
149 }
150 return;
151 }
152 final boolean registerNewAccount = binding.accountRegisterNew.isChecked() && !Config.DISALLOW_REGISTRATION_IN_UI;
153 if (mUsernameMode && binding.accountJid.getText().toString().contains("@")) {
154 binding.accountJidLayout.setError(getString(R.string.invalid_username));
155 removeErrorsOnAllBut(binding.accountJidLayout);
156 binding.accountJid.requestFocus();
157 return;
158 }
159
160 XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
161 boolean openRegistrationUrl = registerNewAccount && !accountInfoEdited && mAccount != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB;
162 boolean openPaymentUrl = mAccount != null && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED;
163 final boolean redirectionWorthyStatus = openPaymentUrl || openRegistrationUrl;
164 URL url = connection != null && redirectionWorthyStatus ? connection.getRedirectionUrl() : null;
165 if (url != null && !wasDisabled) {
166 try {
167 startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url.toString())));
168 return;
169 } catch (ActivityNotFoundException e) {
170 Toast.makeText(EditAccountActivity.this, R.string.application_found_to_open_website, Toast.LENGTH_SHORT).show();
171 return;
172 }
173 }
174
175 final Jid jid;
176 try {
177 if (mUsernameMode) {
178 jid = Jid.of(binding.accountJid.getText().toString(), getUserModeDomain(), null);
179 } else {
180 jid = Jid.of(binding.accountJid.getText().toString());
181 }
182 } catch (final NullPointerException | IllegalArgumentException e) {
183 if (mUsernameMode) {
184 binding.accountJidLayout.setError(getString(R.string.invalid_username));
185 } else {
186 binding.accountJidLayout.setError(getString(R.string.invalid_jid));
187 }
188 binding.accountJid.requestFocus();
189 removeErrorsOnAllBut(binding.accountJidLayout);
190 return;
191 }
192 String hostname = null;
193 int numericPort = 5222;
194 if (mShowOptions) {
195 hostname = binding.hostname.getText().toString().replaceAll("\\s", "");
196 final String port = binding.port.getText().toString().replaceAll("\\s", "");
197 if (hostname.contains(" ")) {
198 binding.hostnameLayout.setError(getString(R.string.not_valid_hostname));
199 binding.hostname.requestFocus();
200 removeErrorsOnAllBut(binding.hostnameLayout);
201 return;
202 }
203 try {
204 numericPort = Integer.parseInt(port);
205 if (numericPort < 0 || numericPort > 65535) {
206 binding.portLayout.setError(getString(R.string.not_a_valid_port));
207 removeErrorsOnAllBut(binding.portLayout);
208 binding.port.requestFocus();
209 return;
210 }
211
212 } catch (NumberFormatException e) {
213 binding.portLayout.setError(getString(R.string.not_a_valid_port));
214 removeErrorsOnAllBut(binding.portLayout);
215 binding.port.requestFocus();
216 return;
217 }
218 }
219
220 if (jid.getLocal() == null) {
221 if (mUsernameMode) {
222 binding.accountJidLayout.setError(getString(R.string.invalid_username));
223 } else {
224 binding.accountJidLayout.setError(getString(R.string.invalid_jid));
225 }
226 removeErrorsOnAllBut(binding.accountJidLayout);
227 binding.accountJid.requestFocus();
228 return;
229 }
230 if (mAccount != null) {
231 if (mInitMode && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
232 mAccount.setOption(Account.OPTION_MAGIC_CREATE, mAccount.getPassword().contains(password));
233 }
234 mAccount.setJid(jid);
235 mAccount.setPort(numericPort);
236 mAccount.setHostname(hostname);
237 binding.accountJidLayout.setError(null);
238 mAccount.setPassword(password);
239 mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
240 if (!xmppConnectionService.updateAccount(mAccount)) {
241 Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
242 return;
243 }
244 } else {
245 if (xmppConnectionService.findAccountByJid(jid) != null) {
246 binding.accountJidLayout.setError(getString(R.string.account_already_exists));
247 removeErrorsOnAllBut(binding.accountJidLayout);
248 binding.accountJid.requestFocus();
249 return;
250 }
251 mAccount = new Account(jid.asBareJid(), password);
252 mAccount.setPort(numericPort);
253 mAccount.setHostname(hostname);
254 mAccount.setOption(Account.OPTION_USETLS, true);
255 mAccount.setOption(Account.OPTION_USECOMPRESSION, true);
256 mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
257 xmppConnectionService.createAccount(mAccount);
258 }
259 binding.hostnameLayout.setError(null);
260 binding.portLayout.setError(null);
261 if (mAccount.isEnabled()
262 && !registerNewAccount
263 && !mInitMode) {
264 finish();
265 } else {
266 updateSaveButton();
267 updateAccountInformation(true);
268 }
269
270 }
271 };
272 private final TextWatcher mTextWatcher = new TextWatcher() {
273
274 @Override
275 public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
276 updatePortLayout();
277 updateSaveButton();
278 }
279
280 @Override
281 public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
282 }
283
284 @Override
285 public void afterTextChanged(final Editable s) {
286
287 }
288 };
289 private View.OnFocusChangeListener mEditTextFocusListener = new View.OnFocusChangeListener() {
290 @Override
291 public void onFocusChange(View view, boolean b) {
292 EditText et = (EditText) view;
293 if (b) {
294 int resId = mUsernameMode ? R.string.username : R.string.account_settings_example_jabber_id;
295 if (view.getId() == R.id.hostname) {
296 resId = mUseTor ? R.string.hostname_or_onion : R.string.hostname_example;
297 }
298 final int res = resId;
299 new Handler().postDelayed(() -> et.setHint(res), 200);
300 } else {
301 et.setHint(null);
302 }
303 }
304 };
305
306 private static void setAvailabilityRadioButton(Presence.Status status, DialogPresenceBinding binding) {
307 if (status == null) {
308 binding.online.setChecked(true);
309 return;
310 }
311 switch (status) {
312 case DND:
313 binding.dnd.setChecked(true);
314 break;
315 case XA:
316 binding.xa.setChecked(true);
317 break;
318 case AWAY:
319 binding.xa.setChecked(true);
320 break;
321 default:
322 binding.online.setChecked(true);
323 }
324 }
325
326 private static Presence.Status getAvailabilityRadioButton(DialogPresenceBinding binding) {
327 if (binding.dnd.isChecked()) {
328 return Presence.Status.DND;
329 } else if (binding.xa.isChecked()) {
330 return Presence.Status.XA;
331 } else if (binding.away.isChecked()) {
332 return Presence.Status.AWAY;
333 } else {
334 return Presence.Status.ONLINE;
335 }
336 }
337
338 public void refreshUiReal() {
339 invalidateOptionsMenu();
340 if (mAccount != null
341 && mAccount.getStatus() != Account.State.ONLINE
342 && mFetchingAvatar) {
343 Intent intent = new Intent(this, StartConversationActivity.class);
344 StartConversationActivity.addInviteUri(intent, getIntent());
345 startActivity(intent);
346 finish();
347 } else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) {
348 if (!mFetchingAvatar) {
349 mFetchingAvatar = true;
350 xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback);
351 }
352 }
353 if (mAccount != null) {
354 updateAccountInformation(false);
355 }
356 updateSaveButton();
357 }
358
359 @Override
360 public boolean onNavigateUp() {
361 deleteAccountAndReturnIfNecessary();
362 return super.onNavigateUp();
363 }
364
365 @Override
366 public void onBackPressed() {
367 deleteAccountAndReturnIfNecessary();
368 super.onBackPressed();
369 }
370
371 private void deleteAccountAndReturnIfNecessary() {
372 if (mInitMode && mAccount != null && !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)) {
373 xmppConnectionService.deleteAccount(mAccount);
374 }
375
376 if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) {
377 Intent intent = SignupUtils.getSignUpIntent(this);
378 startActivity(intent);
379 }
380 }
381
382 @Override
383 public void onAccountUpdate() {
384 refreshUi();
385 }
386
387 protected void finishInitialSetup(final Avatar avatar) {
388 runOnUiThread(() -> {
389 SoftKeyboardUtils.hideSoftKeyboard(EditAccountActivity.this);
390 final Intent intent;
391 final XmppConnection connection = mAccount.getXmppConnection();
392 final boolean wasFirstAccount = xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1;
393 if (avatar != null || (connection != null && !connection.getFeatures().pep())) {
394 intent = new Intent(getApplicationContext(), StartConversationActivity.class);
395 if (wasFirstAccount) {
396 intent.putExtra("init", true);
397 }
398 } else {
399 intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
400 intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().asBareJid().toString());
401 intent.putExtra("setup", true);
402 }
403 if (wasFirstAccount) {
404 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
405 }
406 StartConversationActivity.addInviteUri(intent, getIntent());
407 startActivity(intent);
408 finish();
409 });
410 }
411
412 @Override
413 public void onActivityResult(int requestCode, int resultCode, Intent data) {
414 super.onActivityResult(requestCode, resultCode, data);
415 if (requestCode == REQUEST_BATTERY_OP || requestCode == REQUEST_DATA_SAVER) {
416 updateAccountInformation(mAccount == null);
417 }
418 if (requestCode == REQUEST_CHANGE_STATUS) {
419 PresenceTemplate template = mPendingPresenceTemplate.pop();
420 if (template != null && resultCode == Activity.RESULT_OK) {
421 generateSignature(data, template);
422 } else {
423 Log.d(Config.LOGTAG, "pgp result not ok");
424 }
425 }
426 }
427
428 @Override
429 protected void processFingerprintVerification(XmppUri uri) {
430 processFingerprintVerification(uri, true);
431 }
432
433 protected void processFingerprintVerification(XmppUri uri, boolean showWarningToast) {
434 if (mAccount != null && mAccount.getJid().asBareJid().equals(uri.getJid()) && uri.hasFingerprints()) {
435 if (xmppConnectionService.verifyFingerprints(mAccount, uri.getFingerprints())) {
436 Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
437 updateAccountInformation(false);
438 }
439 } else if (showWarningToast) {
440 Toast.makeText(this, R.string.invalid_barcode, Toast.LENGTH_SHORT).show();
441 }
442 }
443
444 private void updatePortLayout() {
445 String hostname = this.binding.hostname.getText().toString();
446 this.binding.portLayout.setEnabled(!TextUtils.isEmpty(hostname));
447 }
448
449 protected void updateSaveButton() {
450 boolean accountInfoEdited = accountInfoEdited();
451
452 if (!mInitMode && passwordChangedInMagicCreateMode()) {
453 this.binding.saveButton.setText(R.string.change_password);
454 this.binding.saveButton.setEnabled(true);
455 } else if (accountInfoEdited && !mInitMode) {
456 this.binding.saveButton.setText(R.string.save);
457 this.binding.saveButton.setEnabled(true);
458 } else if (mAccount != null
459 && (mAccount.getStatus() == Account.State.CONNECTING || mAccount.getStatus() == Account.State.REGISTRATION_SUCCESSFUL || mFetchingAvatar)) {
460 this.binding.saveButton.setEnabled(false);
461 this.binding.saveButton.setText(R.string.account_status_connecting);
462 } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !mInitMode) {
463 this.binding.saveButton.setEnabled(true);
464 this.binding.saveButton.setText(R.string.enable);
465 } else {
466 this.binding.saveButton.setEnabled(true);
467 if (!mInitMode) {
468 if (mAccount != null && mAccount.isOnlineAndConnected()) {
469 this.binding.saveButton.setText(R.string.save);
470 if (!accountInfoEdited) {
471 this.binding.saveButton.setEnabled(false);
472 }
473 } else {
474 XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
475 URL url = connection != null && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED ? connection.getRedirectionUrl() : null;
476 if (url != null) {
477 this.binding.saveButton.setText(R.string.open_website);
478 } else {
479 this.binding.saveButton.setText(R.string.connect);
480 }
481 }
482 } else {
483 XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection();
484 URL url = connection != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB ? connection.getRedirectionUrl() : null;
485 if (url != null && this.binding.accountRegisterNew.isChecked() && !accountInfoEdited) {
486 this.binding.saveButton.setText(R.string.open_website);
487 } else {
488 this.binding.saveButton.setText(R.string.next);
489 }
490 }
491 }
492 }
493
494 protected boolean accountInfoEdited() {
495 if (this.mAccount == null) {
496 return false;
497 }
498 return jidEdited() ||
499 !this.mAccount.getPassword().equals(this.binding.accountPassword.getText().toString()) ||
500 !this.mAccount.getHostname().equals(this.binding.hostname.getText().toString()) ||
501 !String.valueOf(this.mAccount.getPort()).equals(this.binding.port.getText().toString());
502 }
503
504 protected boolean jidEdited() {
505 final String unmodified;
506 if (mUsernameMode) {
507 unmodified = this.mAccount.getJid().getLocal();
508 } else {
509 unmodified = this.mAccount.getJid().asBareJid().toString();
510 }
511 return !unmodified.equals(this.binding.accountJid.getText().toString());
512 }
513
514 protected boolean passwordChangedInMagicCreateMode() {
515 return mAccount != null
516 && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
517 && !this.mAccount.getPassword().equals(this.binding.accountPassword.getText().toString())
518 && !this.jidEdited()
519 && mAccount.isOnlineAndConnected();
520 }
521
522 @Override
523 protected String getShareableUri(boolean http) {
524 if (mAccount != null) {
525 return http ? mAccount.getShareableLink() : mAccount.getShareableUri();
526 } else {
527 return null;
528 }
529 }
530
531 @Override
532 protected void onCreate(final Bundle savedInstanceState) {
533 super.onCreate(savedInstanceState);
534 if (savedInstanceState != null) {
535 this.mSavedInstanceAccount = savedInstanceState.getString("account");
536 this.mSavedInstanceInit = savedInstanceState.getBoolean("initMode", false);
537 }
538 this.binding = DataBindingUtil.setContentView(this, R.layout.activity_edit_account);
539 setSupportActionBar((Toolbar) binding.toolbar);
540 binding.accountJid.addTextChangedListener(this.mTextWatcher);
541 binding.accountJid.setOnFocusChangeListener(this.mEditTextFocusListener);
542 this.binding.accountPassword.addTextChangedListener(this.mTextWatcher);
543
544 this.binding.avater.setOnClickListener(this.mAvatarClickListener);
545 this.binding.hostname.addTextChangedListener(mTextWatcher);
546 this.binding.hostname.setOnFocusChangeListener(mEditTextFocusListener);
547 this.binding.clearDevices.setOnClickListener(v -> showWipePepDialog());
548 this.binding.port.setText(String.valueOf(Resolver.DEFAULT_PORT_XMPP));
549 this.binding.port.addTextChangedListener(mTextWatcher);
550 this.binding.saveButton.setOnClickListener(this.mSaveButtonClickListener);
551 this.binding.cancelButton.setOnClickListener(this.mCancelButtonClickListener);
552 if (savedInstanceState != null && savedInstanceState.getBoolean("showMoreTable")) {
553 changeMoreTableVisibility(true);
554 }
555 final OnCheckedChangeListener OnCheckedShowConfirmPassword = (buttonView, isChecked) -> updateSaveButton();
556 this.binding.accountRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword);
557 if (Config.DISALLOW_REGISTRATION_IN_UI) {
558 this.binding.accountRegisterNew.setVisibility(View.GONE);
559 }
560 this.binding.yourNameBox.setVisibility(QuickConversationsService.isQuicksy() ? View.VISIBLE : View.GONE);
561 this.binding.actionEditYourName.setOnClickListener(this::onEditYourNameClicked);
562 }
563
564 private void onEditYourNameClicked(View view) {
565 quickEdit(mAccount.getDisplayName(), R.string.your_name, value -> {
566 final String displayName = value.trim();
567 updateDisplayName(displayName);
568 mAccount.setDisplayName(displayName);
569 xmppConnectionService.publishDisplayName(mAccount);
570 return null;
571 }, true);
572 }
573
574 @Override
575 public boolean onCreateOptionsMenu(final Menu menu) {
576 super.onCreateOptionsMenu(menu);
577 getMenuInflater().inflate(R.menu.editaccount, menu);
578 final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list);
579 final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
580 final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
581 final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate);
582 final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs);
583 final MenuItem changePresence = menu.findItem(R.id.action_change_presence);
584 final MenuItem share = menu.findItem(R.id.action_share);
585 renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null);
586
587 share.setVisible(mAccount != null && !mInitMode);
588
589 if (mAccount != null && mAccount.isOnlineAndConnected()) {
590 if (!mAccount.getXmppConnection().getFeatures().blocking()) {
591 showBlocklist.setVisible(false);
592 }
593
594 if (!mAccount.getXmppConnection().getFeatures().register()) {
595 changePassword.setVisible(false);
596 }
597 mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam());
598 changePresence.setVisible(!mInitMode);
599 } else {
600 showBlocklist.setVisible(false);
601 showMoreInfo.setVisible(false);
602 changePassword.setVisible(false);
603 mamPrefs.setVisible(false);
604 changePresence.setVisible(false);
605 }
606 return super.onCreateOptionsMenu(menu);
607 }
608
609 @Override
610 public boolean onPrepareOptionsMenu(Menu menu) {
611 final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
612 if (showMoreInfo.isVisible()) {
613 showMoreInfo.setChecked(binding.serverInfoMore.getVisibility() == View.VISIBLE);
614 }
615 return super.onPrepareOptionsMenu(menu);
616 }
617
618 @Override
619 protected void onStart() {
620 super.onStart();
621 final Intent intent = getIntent();
622 final int theme = findTheme();
623 if (this.mTheme != theme) {
624 recreate();
625 } else if (intent != null) {
626 try {
627 this.jidToEdit = Jid.of(intent.getStringExtra("jid"));
628 } catch (final IllegalArgumentException | NullPointerException ignored) {
629 this.jidToEdit = null;
630 }
631 if (jidToEdit != null && intent.getData() != null && intent.getBooleanExtra("scanned", false)) {
632 final XmppUri uri = new XmppUri(intent.getData());
633 if (xmppConnectionServiceBound) {
634 processFingerprintVerification(uri, false);
635 } else {
636 this.pendingUri = uri;
637 }
638 }
639 boolean init = intent.getBooleanExtra("init", false);
640 boolean openedFromNotification = intent.getBooleanExtra(EXTRA_OPENED_FROM_NOTIFICATION, false);
641 this.mInitMode = init || this.jidToEdit == null;
642 this.messageFingerprint = intent.getStringExtra("fingerprint");
643 if (!mInitMode) {
644 this.binding.accountRegisterNew.setVisibility(View.GONE);
645 setTitle(getString(R.string.account_details));
646 configureActionBar(getSupportActionBar(), !openedFromNotification);
647 } else {
648 this.binding.avater.setVisibility(View.GONE);
649 configureActionBar(getSupportActionBar(), !(init && Config.MAGIC_CREATE_DOMAIN == null));
650 setTitle(R.string.action_add_account);
651 }
652 }
653 SharedPreferences preferences = getPreferences();
654 mUseTor = QuickConversationsService.isConversations() && preferences.getBoolean("use_tor", getResources().getBoolean(R.bool.use_tor));
655 this.mShowOptions = mUseTor || (QuickConversationsService.isConversations() && preferences.getBoolean("show_connection_options", getResources().getBoolean(R.bool.show_connection_options)));
656 this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
657 }
658
659 @Override
660 public void onNewIntent(Intent intent) {
661 if (intent != null && intent.getData() != null) {
662 final XmppUri uri = new XmppUri(intent.getData());
663 if (xmppConnectionServiceBound) {
664 processFingerprintVerification(uri, false);
665 } else {
666 this.pendingUri = uri;
667 }
668 }
669 }
670
671 @Override
672 public void onSaveInstanceState(final Bundle savedInstanceState) {
673 if (mAccount != null) {
674 savedInstanceState.putString("account", mAccount.getJid().asBareJid().toString());
675 savedInstanceState.putBoolean("initMode", mInitMode);
676 savedInstanceState.putBoolean("showMoreTable", binding.serverInfoMore.getVisibility() == View.VISIBLE);
677 }
678 super.onSaveInstanceState(savedInstanceState);
679 }
680
681 protected void onBackendConnected() {
682 boolean init = true;
683 if (mSavedInstanceAccount != null) {
684 try {
685 this.mAccount = xmppConnectionService.findAccountByJid(Jid.of(mSavedInstanceAccount));
686 this.mInitMode = mSavedInstanceInit;
687 init = false;
688 } catch (IllegalArgumentException e) {
689 this.mAccount = null;
690 }
691
692 } else if (this.jidToEdit != null) {
693 this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit);
694 }
695
696 if (mAccount != null) {
697 this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER);
698 this.mUsernameMode |= mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && mAccount.isOptionSet(Account.OPTION_REGISTER);
699 if (this.mAccount.getPrivateKeyAlias() != null) {
700 this.binding.accountPassword.setHint(R.string.authenticate_with_certificate);
701 if (this.mInitMode) {
702 this.binding.accountPassword.requestFocus();
703 }
704 }
705 if (mPendingFingerprintVerificationUri != null) {
706 processFingerprintVerification(mPendingFingerprintVerificationUri, false);
707 mPendingFingerprintVerificationUri = null;
708 }
709 updateAccountInformation(init);
710 }
711
712
713 if (Config.MAGIC_CREATE_DOMAIN == null && this.xmppConnectionService.getAccounts().size() == 0) {
714 this.binding.cancelButton.setEnabled(false);
715 }
716 if (mUsernameMode) {
717 this.binding.accountJidLayout.setHint(getString(R.string.username_hint));
718 this.binding.accountJid.setHint(R.string.username_hint);
719 } else {
720 final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this,
721 R.layout.simple_list_item,
722 xmppConnectionService.getKnownHosts());
723 this.binding.accountJid.setAdapter(mKnownHostsAdapter);
724 }
725
726 if (pendingUri != null) {
727 processFingerprintVerification(pendingUri, false);
728 pendingUri = null;
729 }
730 updatePortLayout();
731 updateSaveButton();
732 invalidateOptionsMenu();
733 }
734
735 private String getUserModeDomain() {
736 if (mAccount != null && mAccount.getJid().getDomain() != null) {
737 return mAccount.getJid().getDomain();
738 } else {
739 return Config.DOMAIN_LOCK;
740 }
741 }
742
743 @Override
744 public boolean onOptionsItemSelected(final MenuItem item) {
745 if (MenuDoubleTabUtil.shouldIgnoreTap()) {
746 return false;
747 }
748 switch (item.getItemId()) {
749 case R.id.action_show_block_list:
750 final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class);
751 showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
752 startActivity(showBlocklistIntent);
753 break;
754 case R.id.action_server_info_show_more:
755 changeMoreTableVisibility(!item.isChecked());
756 break;
757 case R.id.action_share_barcode:
758 shareBarcode();
759 break;
760 case R.id.action_share_http:
761 shareLink(true);
762 break;
763 case R.id.action_share_uri:
764 shareLink(false);
765 break;
766 case R.id.action_change_password_on_server:
767 gotoChangePassword(null);
768 break;
769 case R.id.action_mam_prefs:
770 editMamPrefs();
771 break;
772 case R.id.action_renew_certificate:
773 renewCertificate();
774 break;
775 case R.id.action_change_presence:
776 changePresence();
777 break;
778 }
779 return super.onOptionsItemSelected(item);
780 }
781
782 private void shareBarcode() {
783 Intent intent = new Intent(Intent.ACTION_SEND);
784 intent.putExtra(Intent.EXTRA_STREAM, BarcodeProvider.getUriForAccount(this, mAccount));
785 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
786 intent.setType("image/png");
787 startActivity(Intent.createChooser(intent, getText(R.string.share_with)));
788 }
789
790 private void changeMoreTableVisibility(boolean visible) {
791 binding.serverInfoMore.setVisibility(visible ? View.VISIBLE : View.GONE);
792 }
793
794 private void gotoChangePassword(String newPassword) {
795 final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class);
796 changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
797 if (newPassword != null) {
798 changePasswordIntent.putExtra("password", newPassword);
799 }
800 startActivity(changePasswordIntent);
801 }
802
803 private void renewCertificate() {
804 KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
805 }
806
807 private void changePresence() {
808 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
809 boolean manualStatus = sharedPreferences.getBoolean(SettingsActivity.MANUALLY_CHANGE_PRESENCE, getResources().getBoolean(R.bool.manually_change_presence));
810 AlertDialog.Builder builder = new AlertDialog.Builder(this);
811 final DialogPresenceBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.dialog_presence, null, false);
812 String current = mAccount.getPresenceStatusMessage();
813 if (current != null && !current.trim().isEmpty()) {
814 binding.statusMessage.append(current);
815 }
816 setAvailabilityRadioButton(mAccount.getPresenceStatus(), binding);
817 binding.show.setVisibility(manualStatus ? View.VISIBLE : View.GONE);
818 List<PresenceTemplate> templates = xmppConnectionService.getPresenceTemplates(mAccount);
819 PresenceTemplateAdapter presenceTemplateAdapter = new PresenceTemplateAdapter(this, R.layout.simple_list_item, templates);
820 binding.statusMessage.setAdapter(presenceTemplateAdapter);
821 binding.statusMessage.setOnItemClickListener((parent, view, position, id) -> {
822 PresenceTemplate template = (PresenceTemplate) parent.getItemAtPosition(position);
823 setAvailabilityRadioButton(template.getStatus(), binding);
824 });
825 builder.setTitle(R.string.edit_status_message_title);
826 builder.setView(binding.getRoot());
827 builder.setNegativeButton(R.string.cancel, null);
828 builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
829 PresenceTemplate template = new PresenceTemplate(getAvailabilityRadioButton(binding), binding.statusMessage.getText().toString().trim());
830 if (mAccount.getPgpId() != 0 && hasPgp()) {
831 generateSignature(null, template);
832 } else {
833 xmppConnectionService.changeStatus(mAccount, template, null);
834 }
835 });
836 builder.create().show();
837 }
838
839 private void generateSignature(Intent intent, PresenceTemplate template) {
840 xmppConnectionService.getPgpEngine().generateSignature(intent, mAccount, template.getStatusMessage(), new UiCallback<String>() {
841 @Override
842 public void success(String signature) {
843 xmppConnectionService.changeStatus(mAccount, template, signature);
844 }
845
846 @Override
847 public void error(int errorCode, String object) {
848
849 }
850
851 @Override
852 public void userInputRequried(PendingIntent pi, String object) {
853 mPendingPresenceTemplate.push(template);
854 try {
855 startIntentSenderForResult(pi.getIntentSender(), REQUEST_CHANGE_STATUS, null, 0, 0, 0);
856 } catch (final IntentSender.SendIntentException ignored) {
857 }
858 }
859 });
860 }
861
862 @Override
863 public void alias(String alias) {
864 if (alias != null) {
865 xmppConnectionService.updateKeyInAccount(mAccount, alias);
866 }
867 }
868
869 private void updateAccountInformation(boolean init) {
870 if (init) {
871 this.binding.accountJid.getEditableText().clear();
872 if (mUsernameMode) {
873 this.binding.accountJid.getEditableText().append(this.mAccount.getJid().getLocal());
874 } else {
875 this.binding.accountJid.getEditableText().append(this.mAccount.getJid().asBareJid().toString());
876 }
877 this.binding.accountPassword.getEditableText().clear();
878 this.binding.accountPassword.getEditableText().append(this.mAccount.getPassword());
879 this.binding.hostname.setText("");
880 this.binding.hostname.getEditableText().append(this.mAccount.getHostname());
881 this.binding.port.setText("");
882 this.binding.port.getEditableText().append(String.valueOf(this.mAccount.getPort()));
883 this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
884
885 }
886
887 final boolean editable = !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY) && QuickConversationsService.isConversations();
888 this.binding.accountJid.setEnabled(editable);
889 this.binding.accountJid.setFocusable(editable);
890 this.binding.accountJid.setFocusableInTouchMode(editable);
891
892
893 final String displayName = mAccount.getDisplayName();
894 updateDisplayName(displayName);
895
896
897 if (mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) || !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY)) {
898 this.binding.accountPasswordLayout.setPasswordVisibilityToggleEnabled(true);
899 } else {
900 this.binding.accountPasswordLayout.setPasswordVisibilityToggleEnabled(false);
901 }
902
903 if (!mInitMode) {
904 this.binding.avater.setVisibility(View.VISIBLE);
905 this.binding.avater.setImageBitmap(avatarService().get(this.mAccount, (int) getResources().getDimension(R.dimen.avatar_on_details_screen_size)));
906 } else {
907 this.binding.avater.setVisibility(View.GONE);
908 }
909 this.binding.accountRegisterNew.setChecked(this.mAccount.isOptionSet(Account.OPTION_REGISTER));
910 if (this.mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
911 if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
912 ActionBar actionBar = getSupportActionBar();
913 if (actionBar != null) {
914 actionBar.setTitle(R.string.create_account);
915 }
916 }
917 this.binding.accountRegisterNew.setVisibility(View.GONE);
918 } else if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
919 this.binding.accountRegisterNew.setVisibility(View.VISIBLE);
920 } else {
921 this.binding.accountRegisterNew.setVisibility(View.GONE);
922 }
923 if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
924 Features features = this.mAccount.getXmppConnection().getFeatures();
925 this.binding.stats.setVisibility(View.VISIBLE);
926 boolean showBatteryWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery();
927 boolean showDataSaverWarning = isAffectedByDataSaver();
928 showOsOptimizationWarning(showBatteryWarning, showDataSaverWarning);
929 this.binding.sessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection()
930 .getLastSessionEstablished()));
931 if (features.rosterVersioning()) {
932 this.binding.serverInfoRosterVersion.setText(R.string.server_info_available);
933 } else {
934 this.binding.serverInfoRosterVersion.setText(R.string.server_info_unavailable);
935 }
936 if (features.carbons()) {
937 this.binding.serverInfoCarbons.setText(R.string.server_info_available);
938 } else {
939 this.binding.serverInfoCarbons.setText(R.string.server_info_unavailable);
940 }
941 if (features.mam()) {
942 this.binding.serverInfoMam.setText(R.string.server_info_available);
943 } else {
944 this.binding.serverInfoMam.setText(R.string.server_info_unavailable);
945 }
946 if (features.csi()) {
947 this.binding.serverInfoCsi.setText(R.string.server_info_available);
948 } else {
949 this.binding.serverInfoCsi.setText(R.string.server_info_unavailable);
950 }
951 if (features.blocking()) {
952 this.binding.serverInfoBlocking.setText(R.string.server_info_available);
953 } else {
954 this.binding.serverInfoBlocking.setText(R.string.server_info_unavailable);
955 }
956 if (features.sm()) {
957 this.binding.serverInfoSm.setText(R.string.server_info_available);
958 } else {
959 this.binding.serverInfoSm.setText(R.string.server_info_unavailable);
960 }
961 if (features.pep()) {
962 AxolotlService axolotlService = this.mAccount.getAxolotlService();
963 if (axolotlService != null && axolotlService.isPepBroken()) {
964 this.binding.serverInfoPep.setText(R.string.server_info_broken);
965 } else if (features.pepPublishOptions() || features.pepOmemoWhitelisted()) {
966 this.binding.serverInfoPep.setText(R.string.server_info_available);
967 } else {
968 this.binding.serverInfoPep.setText(R.string.server_info_partial);
969 }
970 } else {
971 this.binding.serverInfoPep.setText(R.string.server_info_unavailable);
972 }
973 if (features.httpUpload(0)) {
974 this.binding.serverInfoHttpUpload.setText(R.string.server_info_available);
975 } else if (features.p1S3FileTransfer()) {
976 this.binding.serverInfoHttpUploadDescription.setText(R.string.p1_s3_filetransfer);
977 this.binding.serverInfoHttpUpload.setText(R.string.server_info_available);
978 } else {
979 this.binding.serverInfoHttpUpload.setText(R.string.server_info_unavailable);
980 }
981
982 this.binding.pushRow.setVisibility(xmppConnectionService.getPushManagementService().isStub() ? View.GONE : View.VISIBLE);
983
984 if (xmppConnectionService.getPushManagementService().available(mAccount)) {
985 this.binding.serverInfoPush.setText(R.string.server_info_available);
986 } else {
987 this.binding.serverInfoPush.setText(R.string.server_info_unavailable);
988 }
989 final long pgpKeyId = this.mAccount.getPgpId();
990 if (pgpKeyId != 0 && Config.supportOpenPgp()) {
991 OnClickListener openPgp = view -> launchOpenKeyChain(pgpKeyId);
992 OnClickListener delete = view -> showDeletePgpDialog();
993 this.binding.pgpFingerprintBox.setVisibility(View.VISIBLE);
994 this.binding.pgpFingerprint.setText(OpenPgpUtils.convertKeyIdToHex(pgpKeyId));
995 this.binding.pgpFingerprint.setOnClickListener(openPgp);
996 if ("pgp".equals(messageFingerprint)) {
997 this.binding.pgpFingerprintDesc.setTextAppearance(this, R.style.TextAppearance_Conversations_Caption_Highlight);
998 }
999 this.binding.pgpFingerprintDesc.setOnClickListener(openPgp);
1000 this.binding.actionDeletePgp.setOnClickListener(delete);
1001 } else {
1002 this.binding.pgpFingerprintBox.setVisibility(View.GONE);
1003 }
1004 final String ownAxolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint();
1005 if (ownAxolotlFingerprint != null && Config.supportOmemo()) {
1006 this.binding.axolotlFingerprintBox.setVisibility(View.VISIBLE);
1007 if (ownAxolotlFingerprint.equals(messageFingerprint)) {
1008 this.binding.ownFingerprintDesc.setTextAppearance(this, R.style.TextAppearance_Conversations_Caption_Highlight);
1009 this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint_selected_message);
1010 } else {
1011 this.binding.ownFingerprintDesc.setTextAppearance(this, R.style.TextAppearance_Conversations_Caption);
1012 this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint);
1013 }
1014 this.binding.axolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2)));
1015 this.binding.actionCopyAxolotlToClipboard.setVisibility(View.VISIBLE);
1016 this.binding.actionCopyAxolotlToClipboard.setOnClickListener(v -> copyOmemoFingerprint(ownAxolotlFingerprint));
1017 } else {
1018 this.binding.axolotlFingerprintBox.setVisibility(View.GONE);
1019 }
1020 boolean hasKeys = false;
1021 binding.otherDeviceKeys.removeAllViews();
1022 for (XmppAxolotlSession session : mAccount.getAxolotlService().findOwnSessions()) {
1023 if (!session.getTrust().isCompromised()) {
1024 boolean highlight = session.getFingerprint().equals(messageFingerprint);
1025 addFingerprintRow(binding.otherDeviceKeys, session, highlight);
1026 hasKeys = true;
1027 }
1028 }
1029 if (hasKeys && Config.supportOmemo()) { //TODO: either the button should be visible if we print an active device or the device list should be fed with reactived devices
1030 this.binding.otherDeviceKeysCard.setVisibility(View.VISIBLE);
1031 Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
1032 if (otherDevices == null || otherDevices.isEmpty()) {
1033 binding.clearDevices.setVisibility(View.GONE);
1034 } else {
1035 binding.clearDevices.setVisibility(View.VISIBLE);
1036 }
1037 } else {
1038 this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
1039 }
1040 } else {
1041 final TextInputLayout errorLayout;
1042 if (this.mAccount.errorStatus()) {
1043 if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) {
1044 errorLayout = this.binding.accountPasswordLayout;
1045 } else if (mShowOptions
1046 && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND
1047 && this.binding.hostname.getText().length() > 0) {
1048 errorLayout = this.binding.hostnameLayout;
1049 } else {
1050 errorLayout = this.binding.accountJidLayout;
1051 }
1052 errorLayout.setError(getString(this.mAccount.getStatus().getReadableId()));
1053 if (init || !accountInfoEdited()) {
1054 errorLayout.requestFocus();
1055 }
1056 } else {
1057 errorLayout = null;
1058 }
1059 removeErrorsOnAllBut(errorLayout);
1060 this.binding.stats.setVisibility(View.GONE);
1061 this.binding.otherDeviceKeysCard.setVisibility(View.GONE);
1062 }
1063 }
1064
1065 private void updateDisplayName(String displayName) {
1066 if (TextUtils.isEmpty(displayName)) {
1067 this.binding.yourName.setText(R.string.no_name_set_instructions);
1068 this.binding.yourName.setTextAppearance(this, R.style.TextAppearance_Conversations_Body1_Tertiary);
1069 } else {
1070 this.binding.yourName.setText(displayName);
1071 this.binding.yourName.setTextAppearance(this, R.style.TextAppearance_Conversations_Body1);
1072 }
1073 }
1074
1075 private void removeErrorsOnAllBut(TextInputLayout exception) {
1076 if (this.binding.accountJidLayout != exception) {
1077 this.binding.accountJidLayout.setErrorEnabled(false);
1078 this.binding.accountJidLayout.setError(null);
1079 }
1080 if (this.binding.accountPasswordLayout != exception) {
1081 this.binding.accountPasswordLayout.setErrorEnabled(false);
1082 this.binding.accountPasswordLayout.setError(null);
1083 }
1084 if (this.binding.hostnameLayout != exception) {
1085 this.binding.hostnameLayout.setErrorEnabled(false);
1086 this.binding.hostnameLayout.setError(null);
1087 }
1088 if (this.binding.portLayout != exception) {
1089 this.binding.portLayout.setErrorEnabled(false);
1090 this.binding.portLayout.setError(null);
1091 }
1092 }
1093
1094 private void showDeletePgpDialog() {
1095 AlertDialog.Builder builder = new AlertDialog.Builder(this);
1096 builder.setTitle(R.string.unpublish_pgp);
1097 builder.setMessage(R.string.unpublish_pgp_message);
1098 builder.setNegativeButton(R.string.cancel, null);
1099 builder.setPositiveButton(R.string.confirm, (dialogInterface, i) -> {
1100 mAccount.setPgpSignId(0);
1101 mAccount.unsetPgpSignature();
1102 xmppConnectionService.databaseBackend.updateAccount(mAccount);
1103 xmppConnectionService.sendPresence(mAccount);
1104 refreshUiReal();
1105 });
1106 builder.create().show();
1107 }
1108
1109 private void showOsOptimizationWarning(boolean showBatteryWarning, boolean showDataSaverWarning) {
1110 this.binding.osOptimization.setVisibility(showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE);
1111 if (showDataSaverWarning) {
1112 this.binding.osOptimizationHeadline.setText(R.string.data_saver_enabled);
1113 this.binding.osOptimizationBody.setText(R.string.data_saver_enabled_explained);
1114 this.binding.osOptimizationDisable.setText(R.string.allow);
1115 this.binding.osOptimizationDisable.setOnClickListener(v -> {
1116 Intent intent = new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS);
1117 Uri uri = Uri.parse("package:" + getPackageName());
1118 intent.setData(uri);
1119 try {
1120 startActivityForResult(intent, REQUEST_DATA_SAVER);
1121 } catch (ActivityNotFoundException e) {
1122 Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_data_saver, Toast.LENGTH_SHORT).show();
1123 }
1124 });
1125 } else if (showBatteryWarning) {
1126 this.binding.osOptimizationDisable.setText(R.string.disable);
1127 this.binding.osOptimizationHeadline.setText(R.string.battery_optimizations_enabled);
1128 this.binding.osOptimizationBody.setText(R.string.battery_optimizations_enabled_explained);
1129 this.binding.osOptimizationDisable.setOnClickListener(v -> {
1130 Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
1131 Uri uri = Uri.parse("package:" + getPackageName());
1132 intent.setData(uri);
1133 try {
1134 startActivityForResult(intent, REQUEST_BATTERY_OP);
1135 } catch (ActivityNotFoundException e) {
1136 Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
1137 }
1138 });
1139 }
1140 }
1141
1142 public void showWipePepDialog() {
1143 Builder builder = new Builder(this);
1144 builder.setTitle(getString(R.string.clear_other_devices));
1145 builder.setIconAttribute(android.R.attr.alertDialogIcon);
1146 builder.setMessage(getString(R.string.clear_other_devices_desc));
1147 builder.setNegativeButton(getString(R.string.cancel), null);
1148 builder.setPositiveButton(getString(R.string.accept),
1149 (dialog, which) -> mAccount.getAxolotlService().wipeOtherPepDevices());
1150 builder.create().show();
1151 }
1152
1153 private void editMamPrefs() {
1154 this.mFetchingMamPrefsToast = Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG);
1155 this.mFetchingMamPrefsToast.show();
1156 xmppConnectionService.fetchMamPreferences(mAccount, this);
1157 }
1158
1159 @Override
1160 public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
1161 refreshUi();
1162 }
1163
1164 @Override
1165 public void onCaptchaRequested(final Account account, final String id, final Data data, final Bitmap captcha) {
1166 runOnUiThread(() -> {
1167 if (mCaptchaDialog != null && mCaptchaDialog.isShowing()) {
1168 mCaptchaDialog.dismiss();
1169 }
1170 final Builder builder = new Builder(EditAccountActivity.this);
1171 final View view = getLayoutInflater().inflate(R.layout.captcha, null);
1172 final ImageView imageView = view.findViewById(R.id.captcha);
1173 final EditText input = view.findViewById(R.id.input);
1174 imageView.setImageBitmap(captcha);
1175
1176 builder.setTitle(getString(R.string.captcha_required));
1177 builder.setView(view);
1178
1179 builder.setPositiveButton(getString(R.string.ok),
1180 (dialog, which) -> {
1181 String rc = input.getText().toString();
1182 data.put("username", account.getUsername());
1183 data.put("password", account.getPassword());
1184 data.put("ocr", rc);
1185 data.submit();
1186
1187 if (xmppConnectionServiceBound) {
1188 xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, id, data);
1189 }
1190 });
1191 builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> {
1192 if (xmppConnectionService != null) {
1193 xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
1194 }
1195 });
1196
1197 builder.setOnCancelListener(dialog -> {
1198 if (xmppConnectionService != null) {
1199 xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
1200 }
1201 });
1202 mCaptchaDialog = builder.create();
1203 mCaptchaDialog.show();
1204 input.requestFocus();
1205 });
1206 }
1207
1208 public void onShowErrorToast(final int resId) {
1209 runOnUiThread(() -> Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show());
1210 }
1211
1212 @Override
1213 public void onPreferencesFetched(final Element prefs) {
1214 runOnUiThread(() -> {
1215 if (mFetchingMamPrefsToast != null) {
1216 mFetchingMamPrefsToast.cancel();
1217 }
1218 Builder builder = new Builder(EditAccountActivity.this);
1219 builder.setTitle(R.string.server_side_mam_prefs);
1220 String defaultAttr = prefs.getAttribute("default");
1221 final List<String> defaults = Arrays.asList("never", "roster", "always");
1222 final AtomicInteger choice = new AtomicInteger(Math.max(0, defaults.indexOf(defaultAttr)));
1223 builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), (dialog, which) -> choice.set(which));
1224 builder.setNegativeButton(R.string.cancel, null);
1225 builder.setPositiveButton(R.string.ok, (dialog, which) -> {
1226 prefs.setAttribute("default", defaults.get(choice.get()));
1227 xmppConnectionService.pushMamPreferences(mAccount, prefs);
1228 });
1229 builder.create().show();
1230 });
1231 }
1232
1233 @Override
1234 public void onPreferencesFetchFailed() {
1235 runOnUiThread(() -> {
1236 if (mFetchingMamPrefsToast != null) {
1237 mFetchingMamPrefsToast.cancel();
1238 }
1239 Toast.makeText(EditAccountActivity.this, R.string.unable_to_fetch_mam_prefs, Toast.LENGTH_LONG).show();
1240 });
1241 }
1242
1243 @Override
1244 public void OnUpdateBlocklist(Status status) {
1245 refreshUi();
1246 }
1247}