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