1package eu.siacs.conversations.ui;
2
3import android.app.AlertDialog;
4import android.app.AlertDialog.Builder;
5import android.app.PendingIntent;
6import android.content.ActivityNotFoundException;
7import android.content.DialogInterface;
8import android.content.Intent;
9import android.content.SharedPreferences;
10import android.graphics.Bitmap;
11import android.net.Uri;
12import android.os.Bundle;
13import android.provider.Settings;
14import android.security.KeyChain;
15import android.security.KeyChainAliasCallback;
16import android.text.Editable;
17import android.text.TextWatcher;
18import android.view.Menu;
19import android.view.MenuItem;
20import android.view.View;
21import android.view.View.OnClickListener;
22import android.widget.AutoCompleteTextView;
23import android.widget.Button;
24import android.widget.CheckBox;
25import android.widget.CompoundButton;
26import android.widget.CompoundButton.OnCheckedChangeListener;
27import android.widget.EditText;
28import android.widget.ImageButton;
29import android.widget.ImageView;
30import android.widget.LinearLayout;
31import android.widget.RelativeLayout;
32import android.widget.TableLayout;
33import android.widget.TableRow;
34import android.widget.TextView;
35import android.widget.Toast;
36
37import android.util.Log;
38
39import java.util.Arrays;
40import java.util.List;
41import java.util.Set;
42import java.util.concurrent.atomic.AtomicInteger;
43
44import eu.siacs.conversations.Config;
45import eu.siacs.conversations.R;
46import eu.siacs.conversations.crypto.axolotl.AxolotlService;
47import eu.siacs.conversations.entities.Account;
48import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested;
49import eu.siacs.conversations.services.XmppConnectionService;
50import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
51import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
52import eu.siacs.conversations.utils.CryptoHelper;
53import eu.siacs.conversations.utils.UIHelper;
54import eu.siacs.conversations.xml.Element;
55import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
56import eu.siacs.conversations.xmpp.XmppConnection;
57import eu.siacs.conversations.xmpp.XmppConnection.Features;
58import eu.siacs.conversations.xmpp.forms.Data;
59import eu.siacs.conversations.xmpp.jid.InvalidJidException;
60import eu.siacs.conversations.xmpp.jid.Jid;
61import eu.siacs.conversations.xmpp.pep.Avatar;
62
63public class EditAccountActivity extends XmppActivity implements OnAccountUpdate,
64 OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched {
65
66 private AutoCompleteTextView mAccountJid;
67 private EditText mPassword;
68 private EditText mPasswordConfirm;
69 private CheckBox mRegisterNew;
70 private Button mCancelButton;
71 private Button mSaveButton;
72 private Button mDisableBatterOptimizations;
73 private TableLayout mMoreTable;
74
75 private LinearLayout mStats;
76 private RelativeLayout mBatteryOptimizations;
77 private TextView mServerInfoSm;
78 private TextView mServerInfoRosterVersion;
79 private TextView mServerInfoCarbons;
80 private TextView mServerInfoMam;
81 private TextView mServerInfoCSI;
82 private TextView mServerInfoBlocking;
83 private TextView mServerInfoPep;
84 private TextView mServerInfoHttpUpload;
85 private TextView mServerInfoPush;
86 private TextView mSessionEst;
87 private TextView mOtrFingerprint;
88 private TextView mAxolotlFingerprint;
89 private TextView mAccountJidLabel;
90 private ImageView mAvatar;
91 private RelativeLayout mOtrFingerprintBox;
92 private RelativeLayout mAxolotlFingerprintBox;
93 private ImageButton mOtrFingerprintToClipboardButton;
94 private ImageButton mAxolotlFingerprintToClipboardButton;
95 private ImageButton mRegenerateAxolotlKeyButton;
96 private LinearLayout keys;
97 private LinearLayout keysCard;
98 private LinearLayout mNamePort;
99 private EditText mHostname;
100 private EditText mPort;
101 private AlertDialog mCaptchaDialog = null;
102
103 private Jid jidToEdit;
104 private boolean mInitMode = false;
105 private boolean mUsernameMode = Config.DOMAIN_LOCK != null;
106 private boolean mShowOptions = false;
107 private Account mAccount;
108 private String messageFingerprint;
109
110 private boolean mFetchingAvatar = false;
111
112 private final OnClickListener mSaveButtonClickListener = new OnClickListener() {
113
114 @Override
115 public void onClick(final View v) {
116 final String password = mPassword.getText().toString();
117 final String passwordConfirm = mPasswordConfirm.getText().toString();
118
119 if (!mInitMode && passwordChangedInMagicCreateMode()) {
120 gotoChangePassword(password);
121 return;
122 }
123 if (mInitMode && mAccount != null) {
124 mAccount.setOption(Account.OPTION_DISABLED, false);
125 }
126 if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited()) {
127 mAccount.setOption(Account.OPTION_DISABLED, false);
128 xmppConnectionService.updateAccount(mAccount);
129 return;
130 }
131 final boolean registerNewAccount = mRegisterNew.isChecked() && !Config.DISALLOW_REGISTRATION_IN_UI;
132 if (mUsernameMode && mAccountJid.getText().toString().contains("@")) {
133 mAccountJid.setError(getString(R.string.invalid_username));
134 mAccountJid.requestFocus();
135 return;
136 }
137 final Jid jid;
138 try {
139 if (mUsernameMode) {
140 jid = Jid.fromParts(mAccountJid.getText().toString(), getUserModeDomain(), null);
141 } else {
142 jid = Jid.fromString(mAccountJid.getText().toString());
143 }
144 } catch (final InvalidJidException e) {
145 if (mUsernameMode) {
146 mAccountJid.setError(getString(R.string.invalid_username));
147 } else {
148 mAccountJid.setError(getString(R.string.invalid_jid));
149 }
150 mAccountJid.requestFocus();
151 return;
152 }
153 String hostname = null;
154 int numericPort = 5222;
155 if (mShowOptions) {
156 hostname = mHostname.getText().toString();
157 final String port = mPort.getText().toString();
158 if (hostname.contains(" ")) {
159 mHostname.setError(getString(R.string.not_valid_hostname));
160 mHostname.requestFocus();
161 return;
162 }
163 try {
164 numericPort = Integer.parseInt(port);
165 if (numericPort < 0 || numericPort > 65535) {
166 mPort.setError(getString(R.string.not_a_valid_port));
167 mPort.requestFocus();
168 return;
169 }
170
171 } catch (NumberFormatException e) {
172 mPort.setError(getString(R.string.not_a_valid_port));
173 mPort.requestFocus();
174 return;
175 }
176 }
177
178 if (jid.isDomainJid()) {
179 if (mUsernameMode) {
180 mAccountJid.setError(getString(R.string.invalid_username));
181 } else {
182 mAccountJid.setError(getString(R.string.invalid_jid));
183 }
184 mAccountJid.requestFocus();
185 return;
186 }
187 if (registerNewAccount) {
188 if (!password.equals(passwordConfirm)) {
189 mPasswordConfirm.setError(getString(R.string.passwords_do_not_match));
190 mPasswordConfirm.requestFocus();
191 return;
192 }
193 }
194 if (mAccount != null) {
195 if (mInitMode && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
196 mAccount.setOption(Account.OPTION_MAGIC_CREATE, mAccount.getPassword().contains(password));
197 }
198 mAccount.setJid(jid);
199 mAccount.setPort(numericPort);
200 mAccount.setHostname(hostname);
201 mAccountJid.setError(null);
202 mPasswordConfirm.setError(null);
203 mAccount.setPassword(password);
204 mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
205 xmppConnectionService.updateAccount(mAccount);
206 } else {
207 if (xmppConnectionService.findAccountByJid(jid) != null) {
208 mAccountJid.setError(getString(R.string.account_already_exists));
209 mAccountJid.requestFocus();
210 return;
211 }
212 mAccount = new Account(jid.toBareJid(), password);
213 mAccount.setPort(numericPort);
214 mAccount.setHostname(hostname);
215 mAccount.setOption(Account.OPTION_USETLS, true);
216 mAccount.setOption(Account.OPTION_USECOMPRESSION, true);
217 mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
218 xmppConnectionService.createAccount(mAccount);
219 }
220 mHostname.setError(null);
221 mPort.setError(null);
222 if (!mAccount.isOptionSet(Account.OPTION_DISABLED)
223 && !registerNewAccount
224 && !mInitMode) {
225 finish();
226 } else {
227 updateSaveButton();
228 updateAccountInformation(true);
229 }
230
231 }
232 };
233 private final OnClickListener mCancelButtonClickListener = new OnClickListener() {
234
235 @Override
236 public void onClick(final View v) {
237 finish();
238 }
239 };
240 private Toast mFetchingMamPrefsToast;
241 private TableRow mPushRow;
242
243 public void refreshUiReal() {
244 invalidateOptionsMenu();
245 if (mAccount != null
246 && mAccount.getStatus() != Account.State.ONLINE
247 && mFetchingAvatar) {
248 startActivity(new Intent(getApplicationContext(),
249 ManageAccountActivity.class));
250 finish();
251 } else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) {
252 if (!mFetchingAvatar) {
253 mFetchingAvatar = true;
254 xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback);
255 }
256 }
257 if (mAccount != null) {
258 updateAccountInformation(false);
259 }
260 updateSaveButton();
261 }
262
263 @Override
264 public void onAccountUpdate() {
265 refreshUi();
266 }
267
268 private final UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() {
269
270 @Override
271 public void userInputRequried(final PendingIntent pi, final Avatar avatar) {
272 finishInitialSetup(avatar);
273 }
274
275 @Override
276 public void success(final Avatar avatar) {
277 finishInitialSetup(avatar);
278 }
279
280 @Override
281 public void error(final int errorCode, final Avatar avatar) {
282 finishInitialSetup(avatar);
283 }
284 };
285 private final TextWatcher mTextWatcher = new TextWatcher() {
286
287 @Override
288 public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
289 updateSaveButton();
290 }
291
292 @Override
293 public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
294 }
295
296 @Override
297 public void afterTextChanged(final Editable s) {
298
299 }
300 };
301
302 private final OnClickListener mAvatarClickListener = new OnClickListener() {
303 @Override
304 public void onClick(final View view) {
305 if (mAccount != null) {
306 final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
307 intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toBareJid().toString());
308 startActivity(intent);
309 }
310 }
311 };
312
313 protected void finishInitialSetup(final Avatar avatar) {
314 runOnUiThread(new Runnable() {
315
316 @Override
317 public void run() {
318 final Intent intent;
319 final XmppConnection connection = mAccount.getXmppConnection();
320 final boolean wasFirstAccount = xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1;
321 if (avatar != null || (connection != null && !connection.getFeatures().pep())) {
322 intent = new Intent(getApplicationContext(), StartConversationActivity.class);
323 if (wasFirstAccount) {
324 intent.putExtra("init", true);
325 }
326 } else {
327 intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
328 intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toBareJid().toString());
329 intent.putExtra("setup", true);
330 }
331 if (wasFirstAccount) {
332 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
333 }
334 startActivity(intent);
335 finish();
336 }
337 });
338 }
339
340 @Override
341 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
342 super.onActivityResult(requestCode, resultCode, data);
343 if (requestCode == REQUEST_BATTERY_OP) {
344 updateAccountInformation(mAccount == null);
345 }
346 }
347
348 protected void updateSaveButton() {
349 boolean accountInfoEdited = accountInfoEdited();
350
351 if (!mInitMode && passwordChangedInMagicCreateMode()) {
352 this.mSaveButton.setText(R.string.change_password);
353 this.mSaveButton.setEnabled(true);
354 this.mSaveButton.setTextColor(getPrimaryTextColor());
355 } else if (accountInfoEdited && !mInitMode) {
356 this.mSaveButton.setText(R.string.save);
357 this.mSaveButton.setEnabled(true);
358 this.mSaveButton.setTextColor(getPrimaryTextColor());
359 } else if (mAccount != null
360 && (mAccount.getStatus() == Account.State.CONNECTING || mAccount.getStatus() == Account.State.REGISTRATION_SUCCESSFUL|| mFetchingAvatar)) {
361 this.mSaveButton.setEnabled(false);
362 this.mSaveButton.setTextColor(getSecondaryTextColor());
363 this.mSaveButton.setText(R.string.account_status_connecting);
364 } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !mInitMode) {
365 this.mSaveButton.setEnabled(true);
366 this.mSaveButton.setTextColor(getPrimaryTextColor());
367 this.mSaveButton.setText(R.string.enable);
368 } else {
369 this.mSaveButton.setEnabled(true);
370 this.mSaveButton.setTextColor(getPrimaryTextColor());
371 if (!mInitMode) {
372 if (mAccount != null && mAccount.isOnlineAndConnected()) {
373 this.mSaveButton.setText(R.string.save);
374 if (!accountInfoEdited) {
375 this.mSaveButton.setEnabled(false);
376 this.mSaveButton.setTextColor(getSecondaryTextColor());
377 }
378 } else {
379 this.mSaveButton.setText(R.string.connect);
380 }
381 } else {
382 this.mSaveButton.setText(R.string.next);
383 }
384 }
385 }
386
387 protected boolean accountInfoEdited() {
388 if (this.mAccount == null) {
389 return false;
390 }
391 return jidEdited() ||
392 !this.mAccount.getPassword().equals(this.mPassword.getText().toString()) ||
393 !this.mAccount.getHostname().equals(this.mHostname.getText().toString()) ||
394 !String.valueOf(this.mAccount.getPort()).equals(this.mPort.getText().toString());
395 }
396
397 protected boolean jidEdited() {
398 final String unmodified;
399 if (mUsernameMode) {
400 unmodified = this.mAccount.getJid().getLocalpart();
401 } else {
402 unmodified = this.mAccount.getJid().toBareJid().toString();
403 }
404 return !unmodified.equals(this.mAccountJid.getText().toString());
405 }
406
407 protected boolean passwordChangedInMagicCreateMode() {
408 return mAccount != null
409 && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
410 && !this.mAccount.getPassword().equals(this.mPassword.getText().toString())
411 && !this.jidEdited()
412 && mAccount.isOnlineAndConnected();
413 }
414
415 @Override
416 protected String getShareableUri() {
417 if (mAccount != null) {
418 return mAccount.getShareableUri();
419 } else {
420 return "";
421 }
422 }
423
424 @Override
425 protected void onCreate(final Bundle savedInstanceState) {
426 super.onCreate(savedInstanceState);
427 setContentView(R.layout.activity_edit_account);
428 this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid);
429 this.mAccountJid.addTextChangedListener(this.mTextWatcher);
430 this.mAccountJidLabel = (TextView) findViewById(R.id.account_jid_label);
431 this.mPassword = (EditText) findViewById(R.id.account_password);
432 this.mPassword.addTextChangedListener(this.mTextWatcher);
433 this.mPasswordConfirm = (EditText) findViewById(R.id.account_password_confirm);
434 this.mAvatar = (ImageView) findViewById(R.id.avater);
435 this.mAvatar.setOnClickListener(this.mAvatarClickListener);
436 this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new);
437 this.mStats = (LinearLayout) findViewById(R.id.stats);
438 this.mBatteryOptimizations = (RelativeLayout) findViewById(R.id.battery_optimization);
439 this.mDisableBatterOptimizations = (Button) findViewById(R.id.batt_op_disable);
440 this.mDisableBatterOptimizations.setOnClickListener(new OnClickListener() {
441 @Override
442 public void onClick(View v) {
443 Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
444 Uri uri = Uri.parse("package:"+getPackageName());
445 intent.setData(uri);
446 try {
447 startActivityForResult(intent, REQUEST_BATTERY_OP);
448 } catch (ActivityNotFoundException e) {
449 Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
450 }
451 }
452 });
453 this.mSessionEst = (TextView) findViewById(R.id.session_est);
454 this.mServerInfoRosterVersion = (TextView) findViewById(R.id.server_info_roster_version);
455 this.mServerInfoCarbons = (TextView) findViewById(R.id.server_info_carbons);
456 this.mServerInfoMam = (TextView) findViewById(R.id.server_info_mam);
457 this.mServerInfoCSI = (TextView) findViewById(R.id.server_info_csi);
458 this.mServerInfoBlocking = (TextView) findViewById(R.id.server_info_blocking);
459 this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm);
460 this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep);
461 this.mServerInfoHttpUpload = (TextView) findViewById(R.id.server_info_http_upload);
462 this.mPushRow = (TableRow) findViewById(R.id.push_row);
463 this.mServerInfoPush = (TextView) findViewById(R.id.server_info_push);
464 this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint);
465 this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box);
466 this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard);
467 this.mAxolotlFingerprint = (TextView) findViewById(R.id.axolotl_fingerprint);
468 this.mAxolotlFingerprintBox = (RelativeLayout) findViewById(R.id.axolotl_fingerprint_box);
469 this.mAxolotlFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_axolotl_to_clipboard);
470 this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_axolotl_key);
471 this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card);
472 this.keys = (LinearLayout) findViewById(R.id.other_device_keys);
473 this.mNamePort = (LinearLayout) findViewById(R.id.name_port);
474 this.mHostname = (EditText) findViewById(R.id.hostname);
475 this.mHostname.addTextChangedListener(mTextWatcher);
476 this.mPort = (EditText) findViewById(R.id.port);
477 this.mPort.setText("5222");
478 this.mPort.addTextChangedListener(mTextWatcher);
479 this.mSaveButton = (Button) findViewById(R.id.save_button);
480 this.mCancelButton = (Button) findViewById(R.id.cancel_button);
481 this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
482 this.mCancelButton.setOnClickListener(this.mCancelButtonClickListener);
483 this.mMoreTable = (TableLayout) findViewById(R.id.server_info_more);
484 final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() {
485 @Override
486 public void onCheckedChanged(final CompoundButton buttonView,
487 final boolean isChecked) {
488 if (isChecked) {
489 mPasswordConfirm.setVisibility(View.VISIBLE);
490 } else {
491 mPasswordConfirm.setVisibility(View.GONE);
492 }
493 updateSaveButton();
494 }
495 };
496 this.mRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword);
497 if (Config.DISALLOW_REGISTRATION_IN_UI) {
498 this.mRegisterNew.setVisibility(View.GONE);
499 }
500 }
501
502 @Override
503 public boolean onCreateOptionsMenu(final Menu menu) {
504 super.onCreateOptionsMenu(menu);
505 getMenuInflater().inflate(R.menu.editaccount, menu);
506 final MenuItem showQrCode = menu.findItem(R.id.action_show_qr_code);
507 final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list);
508 final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
509 final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
510 final MenuItem showPassword = menu.findItem(R.id.action_show_password);
511 final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices);
512 final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate);
513 final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs);
514 final MenuItem changePresence = menu.findItem(R.id.action_change_presence);
515 renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null);
516
517 if (mAccount != null && mAccount.isOnlineAndConnected()) {
518 if (!mAccount.getXmppConnection().getFeatures().blocking()) {
519 showBlocklist.setVisible(false);
520 }
521 if (!mAccount.getXmppConnection().getFeatures().register()) {
522 changePassword.setVisible(false);
523 }
524 mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam());
525 Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
526 if (otherDevices == null || otherDevices.isEmpty() || Config.supportOmemo()) {
527 clearDevices.setVisible(false);
528 }
529 changePresence.setVisible(manuallyChangePresence());
530 } else {
531 showQrCode.setVisible(false);
532 showBlocklist.setVisible(false);
533 showMoreInfo.setVisible(false);
534 changePassword.setVisible(false);
535 clearDevices.setVisible(false);
536 mamPrefs.setVisible(false);
537 changePresence.setVisible(false);
538 }
539
540 if (mAccount != null) {
541 showPassword.setVisible(mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
542 && !mAccount.isOptionSet(Account.OPTION_REGISTER));
543 } else {
544 showPassword.setVisible(false);
545 }
546 return super.onCreateOptionsMenu(menu);
547 }
548
549 @Override
550 protected void onStart() {
551 super.onStart();
552 if (getIntent() != null) {
553 try {
554 this.jidToEdit = Jid.fromString(getIntent().getStringExtra("jid"));
555 } catch (final InvalidJidException | NullPointerException ignored) {
556 this.jidToEdit = null;
557 }
558 this.mInitMode = getIntent().getBooleanExtra("init", false) || this.jidToEdit == null;
559 this.messageFingerprint = getIntent().getStringExtra("fingerprint");
560 if (!mInitMode) {
561 this.mRegisterNew.setVisibility(View.GONE);
562 if (getActionBar() != null) {
563 getActionBar().setTitle(getString(R.string.account_details));
564 }
565 } else {
566 this.mAvatar.setVisibility(View.GONE);
567 if (getActionBar() != null) {
568 getActionBar().setTitle(R.string.action_add_account);
569 }
570 }
571 }
572 SharedPreferences preferences = getPreferences();
573 boolean useTor = Config.FORCE_ORBOT || preferences.getBoolean("use_tor", false);
574 this.mShowOptions = useTor || preferences.getBoolean("show_connection_options", false);
575 mHostname.setHint(useTor ? R.string.hostname_or_onion : R.string.hostname_example);
576 this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
577 }
578
579 @Override
580 protected void onBackendConnected() {
581 if (this.jidToEdit != null) {
582 this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit);
583 if (this.mAccount != null) {
584 this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER);
585 this.mUsernameMode |= mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && mAccount.isOptionSet(Account.OPTION_REGISTER);
586 if (this.mAccount.getPrivateKeyAlias() != null) {
587 this.mPassword.setHint(R.string.authenticate_with_certificate);
588 if (this.mInitMode) {
589 this.mPassword.requestFocus();
590 }
591 }
592 updateAccountInformation(true);
593 }
594 }
595 if ((Config.MAGIC_CREATE_DOMAIN == null && this.xmppConnectionService.getAccounts().size() == 0)
596 || (this.mAccount != null && this.mAccount == xmppConnectionService.getPendingAccount())) {
597 if (getActionBar() != null) {
598 getActionBar().setDisplayHomeAsUpEnabled(false);
599 getActionBar().setDisplayShowHomeEnabled(false);
600 getActionBar().setHomeButtonEnabled(false);
601 }
602 this.mCancelButton.setEnabled(false);
603 this.mCancelButton.setTextColor(getSecondaryTextColor());
604 }
605 if (mUsernameMode) {
606 this.mAccountJidLabel.setText(R.string.username);
607 this.mAccountJid.setHint(R.string.username_hint);
608 } else {
609 final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this,
610 R.layout.simple_list_item,
611 xmppConnectionService.getKnownHosts());
612 this.mAccountJid.setAdapter(mKnownHostsAdapter);
613 }
614 updateSaveButton();
615 invalidateOptionsMenu();
616 }
617
618 private String getUserModeDomain() {
619 if (mAccount != null) {
620 return mAccount.getJid().getDomainpart();
621 } else {
622 return Config.DOMAIN_LOCK;
623 }
624 }
625
626 @Override
627 public boolean onOptionsItemSelected(final MenuItem item) {
628 switch (item.getItemId()) {
629 case R.id.action_show_block_list:
630 final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class);
631 showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
632 startActivity(showBlocklistIntent);
633 break;
634 case R.id.action_server_info_show_more:
635 mMoreTable.setVisibility(item.isChecked() ? View.GONE : View.VISIBLE);
636 item.setChecked(!item.isChecked());
637 break;
638 case R.id.action_change_password_on_server:
639 gotoChangePassword(null);
640 break;
641 case R.id.action_mam_prefs:
642 editMamPrefs();
643 break;
644 case R.id.action_clear_devices:
645 showWipePepDialog();
646 break;
647 case R.id.action_renew_certificate:
648 renewCertificate();
649 break;
650 case R.id.action_change_presence:
651 changePresence();
652 break;
653 case R.id.action_show_password:
654 showPassword();
655 break;
656 }
657 return super.onOptionsItemSelected(item);
658 }
659
660 private void gotoChangePassword(String newPassword) {
661 final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class);
662 changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
663 if (newPassword != null) {
664 changePasswordIntent.putExtra("password", newPassword);
665 }
666 startActivity(changePasswordIntent);
667 }
668
669 private void renewCertificate() {
670 KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
671 }
672
673 private void changePresence() {
674 Intent intent = new Intent(this, SetPresenceActivity.class);
675 intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT,mAccount.getJid().toBareJid().toString());
676 startActivity(intent);
677 }
678
679 @Override
680 public void alias(String alias) {
681 if (alias != null) {
682 xmppConnectionService.updateKeyInAccount(mAccount, alias);
683 }
684 }
685
686 private void updateAccountInformation(boolean init) {
687 if (init) {
688 this.mAccountJid.getEditableText().clear();
689 if (mUsernameMode) {
690 this.mAccountJid.getEditableText().append(this.mAccount.getJid().getLocalpart());
691 } else {
692 this.mAccountJid.getEditableText().append(this.mAccount.getJid().toBareJid().toString());
693 }
694 this.mPassword.setText(this.mAccount.getPassword());
695 this.mHostname.setText("");
696 this.mHostname.getEditableText().append(this.mAccount.getHostname());
697 this.mPort.setText("");
698 this.mPort.getEditableText().append(String.valueOf(this.mAccount.getPort()));
699 this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
700
701 }
702
703 if (!mInitMode) {
704 this.mAvatar.setVisibility(View.VISIBLE);
705 this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(72)));
706 } else {
707 this.mAvatar.setVisibility(View.GONE);
708 }
709 if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
710 this.mRegisterNew.setVisibility(View.VISIBLE);
711 this.mRegisterNew.setChecked(true);
712 this.mPasswordConfirm.setText(this.mAccount.getPassword());
713 } else {
714 this.mRegisterNew.setVisibility(View.GONE);
715 this.mRegisterNew.setChecked(false);
716 }
717 if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
718 Features features = this.mAccount.getXmppConnection().getFeatures();
719 this.mStats.setVisibility(View.VISIBLE);
720 boolean showOptimizingWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery();
721 this.mBatteryOptimizations.setVisibility(showOptimizingWarning ? View.VISIBLE : View.GONE);
722 this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection()
723 .getLastSessionEstablished()));
724 if (features.rosterVersioning()) {
725 this.mServerInfoRosterVersion.setText(R.string.server_info_available);
726 } else {
727 this.mServerInfoRosterVersion.setText(R.string.server_info_unavailable);
728 }
729 if (features.carbons()) {
730 this.mServerInfoCarbons.setText(R.string.server_info_available);
731 } else {
732 this.mServerInfoCarbons
733 .setText(R.string.server_info_unavailable);
734 }
735 if (features.mam()) {
736 this.mServerInfoMam.setText(R.string.server_info_available);
737 } else {
738 this.mServerInfoMam.setText(R.string.server_info_unavailable);
739 }
740 if (features.csi()) {
741 this.mServerInfoCSI.setText(R.string.server_info_available);
742 } else {
743 this.mServerInfoCSI.setText(R.string.server_info_unavailable);
744 }
745 if (features.blocking()) {
746 this.mServerInfoBlocking.setText(R.string.server_info_available);
747 } else {
748 this.mServerInfoBlocking.setText(R.string.server_info_unavailable);
749 }
750 if (features.sm()) {
751 this.mServerInfoSm.setText(R.string.server_info_available);
752 } else {
753 this.mServerInfoSm.setText(R.string.server_info_unavailable);
754 }
755 if (features.pep()) {
756 AxolotlService axolotlService = this.mAccount.getAxolotlService();
757 if (axolotlService != null && axolotlService.isPepBroken()) {
758 this.mServerInfoPep.setText(R.string.server_info_broken);
759 } else {
760 this.mServerInfoPep.setText(R.string.server_info_available);
761 }
762 } else {
763 this.mServerInfoPep.setText(R.string.server_info_unavailable);
764 }
765 if (features.httpUpload(0)) {
766 this.mServerInfoHttpUpload.setText(R.string.server_info_available);
767 } else {
768 this.mServerInfoHttpUpload.setText(R.string.server_info_unavailable);
769 }
770
771 this.mPushRow.setVisibility(xmppConnectionService.getPushManagementService().isStub() ? View.GONE : View.VISIBLE);
772
773 if (xmppConnectionService.getPushManagementService().available(mAccount)) {
774 this.mServerInfoPush.setText(R.string.server_info_available);
775 } else {
776 this.mServerInfoPush.setText(R.string.server_info_unavailable);
777 }
778 final String otrFingerprint = this.mAccount.getOtrFingerprint();
779 if (otrFingerprint != null && Config.supportOtr()) {
780 this.mOtrFingerprintBox.setVisibility(View.VISIBLE);
781 this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(otrFingerprint));
782 this.mOtrFingerprintToClipboardButton
783 .setVisibility(View.VISIBLE);
784 this.mOtrFingerprintToClipboardButton
785 .setOnClickListener(new View.OnClickListener() {
786
787 @Override
788 public void onClick(final View v) {
789
790 if (copyTextToClipboard(otrFingerprint, R.string.otr_fingerprint)) {
791 Toast.makeText(
792 EditAccountActivity.this,
793 R.string.toast_message_otr_fingerprint,
794 Toast.LENGTH_SHORT).show();
795 }
796 }
797 });
798 } else {
799 this.mOtrFingerprintBox.setVisibility(View.GONE);
800 }
801 final String ownAxolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint();
802 if (ownAxolotlFingerprint != null && Config.supportOmemo()) {
803 this.mAxolotlFingerprintBox.setVisibility(View.VISIBLE);
804 this.mAxolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2)));
805 this.mAxolotlFingerprintToClipboardButton
806 .setVisibility(View.VISIBLE);
807 this.mAxolotlFingerprintToClipboardButton
808 .setOnClickListener(new View.OnClickListener() {
809
810 @Override
811 public void onClick(final View v) {
812
813 if (copyTextToClipboard(ownAxolotlFingerprint.substring(2), R.string.omemo_fingerprint)) {
814 Toast.makeText(
815 EditAccountActivity.this,
816 R.string.toast_message_omemo_fingerprint,
817 Toast.LENGTH_SHORT).show();
818 }
819 }
820 });
821 if (Config.SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON) {
822 this.mRegenerateAxolotlKeyButton
823 .setVisibility(View.VISIBLE);
824 this.mRegenerateAxolotlKeyButton
825 .setOnClickListener(new View.OnClickListener() {
826
827 @Override
828 public void onClick(final View v) {
829 showRegenerateAxolotlKeyDialog();
830 }
831 });
832 }
833 } else {
834 this.mAxolotlFingerprintBox.setVisibility(View.GONE);
835 }
836 boolean hasKeys = false;
837 keys.removeAllViews();
838 for (final String fingerprint : mAccount.getAxolotlService().getFingerprintsForOwnSessions()) {
839 if (ownAxolotlFingerprint.equals(fingerprint)) {
840 continue;
841 }
842 boolean highlight = fingerprint.equals(messageFingerprint);
843 hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight, null);
844 }
845 if (hasKeys && Config.supportOmemo()) {
846 keysCard.setVisibility(View.VISIBLE);
847 } else {
848 keysCard.setVisibility(View.GONE);
849 }
850 } else {
851 if (this.mAccount.errorStatus()) {
852 final EditText errorTextField;
853 if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) {
854 errorTextField = this.mPassword;
855 } else if (mShowOptions
856 && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND
857 && this.mHostname.getText().length() > 0) {
858 errorTextField = this.mHostname;
859 } else {
860 errorTextField = this.mAccountJid;
861 }
862 errorTextField.setError(getString(this.mAccount.getStatus().getReadableId()));
863 if (init || !accountInfoEdited()) {
864 errorTextField.requestFocus();
865 }
866 } else {
867 this.mAccountJid.setError(null);
868 this.mPassword.setError(null);
869 this.mHostname.setError(null);
870 }
871 this.mStats.setVisibility(View.GONE);
872 }
873 }
874
875 public void showRegenerateAxolotlKeyDialog() {
876 Builder builder = new Builder(this);
877 builder.setTitle("Regenerate Key");
878 builder.setIconAttribute(android.R.attr.alertDialogIcon);
879 builder.setMessage("Are you sure you want to regenerate your Identity Key? (This will also wipe all established sessions and contact Identity Keys)");
880 builder.setNegativeButton(getString(R.string.cancel), null);
881 builder.setPositiveButton("Yes",
882 new DialogInterface.OnClickListener() {
883 @Override
884 public void onClick(DialogInterface dialog, int which) {
885 mAccount.getAxolotlService().regenerateKeys(false);
886 }
887 });
888 builder.create().show();
889 }
890
891 public void showWipePepDialog() {
892 Builder builder = new Builder(this);
893 builder.setTitle(getString(R.string.clear_other_devices));
894 builder.setIconAttribute(android.R.attr.alertDialogIcon);
895 builder.setMessage(getString(R.string.clear_other_devices_desc));
896 builder.setNegativeButton(getString(R.string.cancel), null);
897 builder.setPositiveButton(getString(R.string.accept),
898 new DialogInterface.OnClickListener() {
899 @Override
900 public void onClick(DialogInterface dialog, int which) {
901 mAccount.getAxolotlService().wipeOtherPepDevices();
902 }
903 });
904 builder.create().show();
905 }
906
907 private void editMamPrefs() {
908 this.mFetchingMamPrefsToast = Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG);
909 this.mFetchingMamPrefsToast.show();
910 xmppConnectionService.fetchMamPreferences(mAccount, this);
911 }
912
913 private void showPassword() {
914 AlertDialog.Builder builder = new AlertDialog.Builder(this);
915 View view = getLayoutInflater().inflate(R.layout.dialog_show_password, null);
916 TextView password = (TextView) view.findViewById(R.id.password);
917 password.setText(mAccount.getPassword());
918 builder.setTitle(R.string.password);
919 builder.setView(view);
920 builder.setPositiveButton(R.string.cancel, null);
921 builder.create().show();
922 }
923
924 @Override
925 public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
926 refreshUi();
927 }
928
929 @Override
930 public void onCaptchaRequested(final Account account, final String id, final Data data, final Bitmap captcha) {
931 runOnUiThread(new Runnable() {
932 @Override
933 public void run() {
934 if ((mCaptchaDialog != null) && mCaptchaDialog.isShowing()) {
935 mCaptchaDialog.dismiss();
936 }
937 final AlertDialog.Builder builder = new AlertDialog.Builder(EditAccountActivity.this);
938 final View view = getLayoutInflater().inflate(R.layout.captcha, null);
939 final ImageView imageView = (ImageView) view.findViewById(R.id.captcha);
940 final EditText input = (EditText) view.findViewById(R.id.input);
941 imageView.setImageBitmap(captcha);
942
943 builder.setTitle(getString(R.string.captcha_required));
944 builder.setView(view);
945
946 builder.setPositiveButton(getString(R.string.ok),
947 new DialogInterface.OnClickListener() {
948 @Override
949 public void onClick(DialogInterface dialog, int which) {
950 String rc = input.getText().toString();
951 data.put("username", account.getUsername());
952 data.put("password", account.getPassword());
953 data.put("ocr", rc);
954 data.submit();
955
956 if (xmppConnectionServiceBound) {
957 xmppConnectionService.sendCreateAccountWithCaptchaPacket(
958 account, id, data);
959 }
960 }
961 });
962 builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
963 @Override
964 public void onClick(DialogInterface dialog, int which) {
965 if (xmppConnectionService != null) {
966 xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
967 }
968 }
969 });
970
971 builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
972 @Override
973 public void onCancel(DialogInterface dialog) {
974 if (xmppConnectionService != null) {
975 xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
976 }
977 }
978 });
979 mCaptchaDialog = builder.create();
980 mCaptchaDialog.show();
981 }
982 });
983 }
984
985 public void onShowErrorToast(final int resId) {
986 runOnUiThread(new Runnable() {
987 @Override
988 public void run() {
989 Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show();
990 }
991 });
992 }
993
994 @Override
995 public void onPreferencesFetched(final Element prefs) {
996 runOnUiThread(new Runnable() {
997 @Override
998 public void run() {
999 if (mFetchingMamPrefsToast != null) {
1000 mFetchingMamPrefsToast.cancel();
1001 }
1002 AlertDialog.Builder builder = new Builder(EditAccountActivity.this);
1003 builder.setTitle(R.string.server_side_mam_prefs);
1004 String defaultAttr = prefs.getAttribute("default");
1005 final List<String> defaults = Arrays.asList("never", "roster", "always");
1006 final AtomicInteger choice = new AtomicInteger(Math.max(0,defaults.indexOf(defaultAttr)));
1007 builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), new DialogInterface.OnClickListener() {
1008 @Override
1009 public void onClick(DialogInterface dialog, int which) {
1010 choice.set(which);
1011 }
1012 });
1013 builder.setNegativeButton(R.string.cancel, null);
1014 builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
1015 @Override
1016 public void onClick(DialogInterface dialog, int which) {
1017 prefs.setAttribute("default",defaults.get(choice.get()));
1018 xmppConnectionService.pushMamPreferences(mAccount, prefs);
1019 }
1020 });
1021 builder.create().show();
1022 }
1023 });
1024 }
1025
1026 @Override
1027 public void onPreferencesFetchFailed() {
1028 runOnUiThread(new Runnable() {
1029 @Override
1030 public void run() {
1031 if (mFetchingMamPrefsToast != null) {
1032 mFetchingMamPrefsToast.cancel();
1033 }
1034 Toast.makeText(EditAccountActivity.this,R.string.unable_to_fetch_mam_prefs,Toast.LENGTH_LONG).show();
1035 }
1036 });
1037 }
1038}