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