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