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