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