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