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 if (mSavedInstanceAccount != null) {
630 try {
631 this.mAccount = xmppConnectionService.findAccountByJid(Jid.fromString(mSavedInstanceAccount));
632 this.mInitMode = mSavedInstanceInit;
633 } catch (InvalidJidException e) {
634 this.mAccount = null;
635 }
636
637 } else if (this.jidToEdit != null) {
638 this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit);
639 }
640
641 if (mAccount != null) {
642 this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER);
643 this.mUsernameMode |= mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && mAccount.isOptionSet(Account.OPTION_REGISTER);
644 if (this.mAccount.getPrivateKeyAlias() != null) {
645 this.mPassword.setHint(R.string.authenticate_with_certificate);
646 if (this.mInitMode) {
647 this.mPassword.requestFocus();
648 }
649 }
650 updateAccountInformation(true);
651 }
652
653
654 if (Config.MAGIC_CREATE_DOMAIN == null && this.xmppConnectionService.getAccounts().size() == 0) {
655 this.mCancelButton.setEnabled(false);
656 this.mCancelButton.setTextColor(getSecondaryTextColor());
657 }
658 if (mUsernameMode) {
659 this.mAccountJidLabel.setText(R.string.username);
660 this.mAccountJid.setHint(R.string.username_hint);
661 } else {
662 final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this,
663 R.layout.simple_list_item,
664 xmppConnectionService.getKnownHosts());
665 this.mAccountJid.setAdapter(mKnownHostsAdapter);
666 }
667 updateSaveButton();
668 invalidateOptionsMenu();
669 }
670
671 private String getUserModeDomain() {
672 if (mAccount != null) {
673 return mAccount.getJid().getDomainpart();
674 } else {
675 return Config.DOMAIN_LOCK;
676 }
677 }
678
679 @Override
680 public boolean onOptionsItemSelected(final MenuItem item) {
681 switch (item.getItemId()) {
682 case R.id.action_show_block_list:
683 final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class);
684 showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
685 startActivity(showBlocklistIntent);
686 break;
687 case R.id.action_server_info_show_more:
688 mMoreTable.setVisibility(item.isChecked() ? View.GONE : View.VISIBLE);
689 item.setChecked(!item.isChecked());
690 break;
691 case R.id.action_change_password_on_server:
692 gotoChangePassword(null);
693 break;
694 case R.id.action_mam_prefs:
695 editMamPrefs();
696 break;
697 case R.id.action_clear_devices:
698 showWipePepDialog();
699 break;
700 case R.id.action_renew_certificate:
701 renewCertificate();
702 break;
703 case R.id.action_change_presence:
704 changePresence();
705 break;
706 case R.id.action_show_password:
707 showPassword();
708 break;
709 }
710 return super.onOptionsItemSelected(item);
711 }
712
713 private void gotoChangePassword(String newPassword) {
714 final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class);
715 changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
716 if (newPassword != null) {
717 changePasswordIntent.putExtra("password", newPassword);
718 }
719 startActivity(changePasswordIntent);
720 }
721
722 private void renewCertificate() {
723 KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
724 }
725
726 private void changePresence() {
727 Intent intent = new Intent(this, SetPresenceActivity.class);
728 intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT,mAccount.getJid().toBareJid().toString());
729 startActivity(intent);
730 }
731
732 @Override
733 public void alias(String alias) {
734 if (alias != null) {
735 xmppConnectionService.updateKeyInAccount(mAccount, alias);
736 }
737 }
738
739 private void updateAccountInformation(boolean init) {
740 if (init) {
741 this.mAccountJid.getEditableText().clear();
742 if (mUsernameMode) {
743 this.mAccountJid.getEditableText().append(this.mAccount.getJid().getLocalpart());
744 } else {
745 this.mAccountJid.getEditableText().append(this.mAccount.getJid().toBareJid().toString());
746 }
747 this.mPassword.setText(this.mAccount.getPassword());
748 this.mHostname.setText("");
749 this.mHostname.getEditableText().append(this.mAccount.getHostname());
750 this.mPort.setText("");
751 this.mPort.getEditableText().append(String.valueOf(this.mAccount.getPort()));
752 this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
753
754 }
755
756 if (!mInitMode) {
757 this.mAvatar.setVisibility(View.VISIBLE);
758 this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(72)));
759 } else {
760 this.mAvatar.setVisibility(View.GONE);
761 }
762 if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
763 this.mRegisterNew.setVisibility(View.VISIBLE);
764 this.mRegisterNew.setChecked(true);
765 this.mPasswordConfirm.setText(this.mAccount.getPassword());
766 } else {
767 this.mRegisterNew.setVisibility(View.GONE);
768 this.mRegisterNew.setChecked(false);
769 }
770 if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
771 Features features = this.mAccount.getXmppConnection().getFeatures();
772 this.mStats.setVisibility(View.VISIBLE);
773 boolean showOptimizingWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery();
774 this.mBatteryOptimizations.setVisibility(showOptimizingWarning ? View.VISIBLE : View.GONE);
775 this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection()
776 .getLastSessionEstablished()));
777 if (features.rosterVersioning()) {
778 this.mServerInfoRosterVersion.setText(R.string.server_info_available);
779 } else {
780 this.mServerInfoRosterVersion.setText(R.string.server_info_unavailable);
781 }
782 if (features.carbons()) {
783 this.mServerInfoCarbons.setText(R.string.server_info_available);
784 } else {
785 this.mServerInfoCarbons
786 .setText(R.string.server_info_unavailable);
787 }
788 if (features.mam()) {
789 this.mServerInfoMam.setText(R.string.server_info_available);
790 } else {
791 this.mServerInfoMam.setText(R.string.server_info_unavailable);
792 }
793 if (features.csi()) {
794 this.mServerInfoCSI.setText(R.string.server_info_available);
795 } else {
796 this.mServerInfoCSI.setText(R.string.server_info_unavailable);
797 }
798 if (features.blocking()) {
799 this.mServerInfoBlocking.setText(R.string.server_info_available);
800 } else {
801 this.mServerInfoBlocking.setText(R.string.server_info_unavailable);
802 }
803 if (features.sm()) {
804 this.mServerInfoSm.setText(R.string.server_info_available);
805 } else {
806 this.mServerInfoSm.setText(R.string.server_info_unavailable);
807 }
808 if (features.pep()) {
809 AxolotlService axolotlService = this.mAccount.getAxolotlService();
810 if (axolotlService != null && axolotlService.isPepBroken()) {
811 this.mServerInfoPep.setText(R.string.server_info_broken);
812 } else {
813 this.mServerInfoPep.setText(R.string.server_info_available);
814 }
815 } else {
816 this.mServerInfoPep.setText(R.string.server_info_unavailable);
817 }
818 if (features.httpUpload(0)) {
819 this.mServerInfoHttpUpload.setText(R.string.server_info_available);
820 } else {
821 this.mServerInfoHttpUpload.setText(R.string.server_info_unavailable);
822 }
823
824 this.mPushRow.setVisibility(xmppConnectionService.getPushManagementService().isStub() ? View.GONE : View.VISIBLE);
825
826 if (xmppConnectionService.getPushManagementService().available(mAccount)) {
827 this.mServerInfoPush.setText(R.string.server_info_available);
828 } else {
829 this.mServerInfoPush.setText(R.string.server_info_unavailable);
830 }
831 final String otrFingerprint = this.mAccount.getOtrFingerprint();
832 if (otrFingerprint != null && Config.supportOtr()) {
833 this.mOtrFingerprintBox.setVisibility(View.VISIBLE);
834 this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(otrFingerprint));
835 this.mOtrFingerprintToClipboardButton
836 .setVisibility(View.VISIBLE);
837 this.mOtrFingerprintToClipboardButton
838 .setOnClickListener(new View.OnClickListener() {
839
840 @Override
841 public void onClick(final View v) {
842
843 if (copyTextToClipboard(otrFingerprint, R.string.otr_fingerprint)) {
844 Toast.makeText(
845 EditAccountActivity.this,
846 R.string.toast_message_otr_fingerprint,
847 Toast.LENGTH_SHORT).show();
848 }
849 }
850 });
851 } else {
852 this.mOtrFingerprintBox.setVisibility(View.GONE);
853 }
854 final String ownAxolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint();
855 if (ownAxolotlFingerprint != null && Config.supportOmemo()) {
856 this.mAxolotlFingerprintBox.setVisibility(View.VISIBLE);
857 if (ownAxolotlFingerprint.equals(messageFingerprint)) {
858 this.mOwnFingerprintDesc.setTextColor(getResources().getColor(R.color.accent));
859 } else {
860 this.mOwnFingerprintDesc.setTextColor(getSecondaryTextColor());
861 }
862 this.mAxolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2)));
863 this.mAxolotlFingerprintToClipboardButton
864 .setVisibility(View.VISIBLE);
865 this.mAxolotlFingerprintToClipboardButton
866 .setOnClickListener(new View.OnClickListener() {
867
868 @Override
869 public void onClick(final View v) {
870
871 if (copyTextToClipboard(ownAxolotlFingerprint.substring(2), R.string.omemo_fingerprint)) {
872 Toast.makeText(
873 EditAccountActivity.this,
874 R.string.toast_message_omemo_fingerprint,
875 Toast.LENGTH_SHORT).show();
876 }
877 }
878 });
879 if (Config.SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON) {
880 this.mRegenerateAxolotlKeyButton
881 .setVisibility(View.VISIBLE);
882 this.mRegenerateAxolotlKeyButton
883 .setOnClickListener(new View.OnClickListener() {
884
885 @Override
886 public void onClick(final View v) {
887 showRegenerateAxolotlKeyDialog();
888 }
889 });
890 }
891 } else {
892 this.mAxolotlFingerprintBox.setVisibility(View.GONE);
893 }
894 boolean hasKeys = false;
895 keys.removeAllViews();
896 for (final String fingerprint : mAccount.getAxolotlService().getFingerprintsForOwnSessions()) {
897 if (ownAxolotlFingerprint.equals(fingerprint)) {
898 continue;
899 }
900 boolean highlight = fingerprint.equals(messageFingerprint);
901 hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight, null);
902 }
903 if (hasKeys && Config.supportOmemo()) {
904 keysCard.setVisibility(View.VISIBLE);
905 } else {
906 keysCard.setVisibility(View.GONE);
907 }
908 } else {
909 if (this.mAccount.errorStatus()) {
910 final EditText errorTextField;
911 if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) {
912 errorTextField = this.mPassword;
913 } else if (mShowOptions
914 && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND
915 && this.mHostname.getText().length() > 0) {
916 errorTextField = this.mHostname;
917 } else {
918 errorTextField = this.mAccountJid;
919 }
920 errorTextField.setError(getString(this.mAccount.getStatus().getReadableId()));
921 if (init || !accountInfoEdited()) {
922 errorTextField.requestFocus();
923 }
924 } else {
925 this.mAccountJid.setError(null);
926 this.mPassword.setError(null);
927 this.mHostname.setError(null);
928 }
929 this.mStats.setVisibility(View.GONE);
930 }
931 }
932
933 public void showRegenerateAxolotlKeyDialog() {
934 Builder builder = new Builder(this);
935 builder.setTitle("Regenerate Key");
936 builder.setIconAttribute(android.R.attr.alertDialogIcon);
937 builder.setMessage("Are you sure you want to regenerate your Identity Key? (This will also wipe all established sessions and contact Identity Keys)");
938 builder.setNegativeButton(getString(R.string.cancel), null);
939 builder.setPositiveButton("Yes",
940 new DialogInterface.OnClickListener() {
941 @Override
942 public void onClick(DialogInterface dialog, int which) {
943 mAccount.getAxolotlService().regenerateKeys(false);
944 }
945 });
946 builder.create().show();
947 }
948
949 public void showWipePepDialog() {
950 Builder builder = new Builder(this);
951 builder.setTitle(getString(R.string.clear_other_devices));
952 builder.setIconAttribute(android.R.attr.alertDialogIcon);
953 builder.setMessage(getString(R.string.clear_other_devices_desc));
954 builder.setNegativeButton(getString(R.string.cancel), null);
955 builder.setPositiveButton(getString(R.string.accept),
956 new DialogInterface.OnClickListener() {
957 @Override
958 public void onClick(DialogInterface dialog, int which) {
959 mAccount.getAxolotlService().wipeOtherPepDevices();
960 }
961 });
962 builder.create().show();
963 }
964
965 private void editMamPrefs() {
966 this.mFetchingMamPrefsToast = Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG);
967 this.mFetchingMamPrefsToast.show();
968 xmppConnectionService.fetchMamPreferences(mAccount, this);
969 }
970
971 private void showPassword() {
972 AlertDialog.Builder builder = new AlertDialog.Builder(this);
973 View view = getLayoutInflater().inflate(R.layout.dialog_show_password, null);
974 TextView password = (TextView) view.findViewById(R.id.password);
975 password.setText(mAccount.getPassword());
976 builder.setTitle(R.string.password);
977 builder.setView(view);
978 builder.setPositiveButton(R.string.cancel, null);
979 builder.create().show();
980 }
981
982 @Override
983 public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
984 refreshUi();
985 }
986
987 @Override
988 public void onCaptchaRequested(final Account account, final String id, final Data data, final Bitmap captcha) {
989 runOnUiThread(new Runnable() {
990 @Override
991 public void run() {
992 if ((mCaptchaDialog != null) && mCaptchaDialog.isShowing()) {
993 mCaptchaDialog.dismiss();
994 }
995 final AlertDialog.Builder builder = new AlertDialog.Builder(EditAccountActivity.this);
996 final View view = getLayoutInflater().inflate(R.layout.captcha, null);
997 final ImageView imageView = (ImageView) view.findViewById(R.id.captcha);
998 final EditText input = (EditText) view.findViewById(R.id.input);
999 imageView.setImageBitmap(captcha);
1000
1001 builder.setTitle(getString(R.string.captcha_required));
1002 builder.setView(view);
1003
1004 builder.setPositiveButton(getString(R.string.ok),
1005 new DialogInterface.OnClickListener() {
1006 @Override
1007 public void onClick(DialogInterface dialog, int which) {
1008 String rc = input.getText().toString();
1009 data.put("username", account.getUsername());
1010 data.put("password", account.getPassword());
1011 data.put("ocr", rc);
1012 data.submit();
1013
1014 if (xmppConnectionServiceBound) {
1015 xmppConnectionService.sendCreateAccountWithCaptchaPacket(
1016 account, id, data);
1017 }
1018 }
1019 });
1020 builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
1021 @Override
1022 public void onClick(DialogInterface dialog, int which) {
1023 if (xmppConnectionService != null) {
1024 xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
1025 }
1026 }
1027 });
1028
1029 builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
1030 @Override
1031 public void onCancel(DialogInterface dialog) {
1032 if (xmppConnectionService != null) {
1033 xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
1034 }
1035 }
1036 });
1037 mCaptchaDialog = builder.create();
1038 mCaptchaDialog.show();
1039 }
1040 });
1041 }
1042
1043 public void onShowErrorToast(final int resId) {
1044 runOnUiThread(new Runnable() {
1045 @Override
1046 public void run() {
1047 Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show();
1048 }
1049 });
1050 }
1051
1052 @Override
1053 public void onPreferencesFetched(final Element prefs) {
1054 runOnUiThread(new Runnable() {
1055 @Override
1056 public void run() {
1057 if (mFetchingMamPrefsToast != null) {
1058 mFetchingMamPrefsToast.cancel();
1059 }
1060 AlertDialog.Builder builder = new Builder(EditAccountActivity.this);
1061 builder.setTitle(R.string.server_side_mam_prefs);
1062 String defaultAttr = prefs.getAttribute("default");
1063 final List<String> defaults = Arrays.asList("never", "roster", "always");
1064 final AtomicInteger choice = new AtomicInteger(Math.max(0,defaults.indexOf(defaultAttr)));
1065 builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), new DialogInterface.OnClickListener() {
1066 @Override
1067 public void onClick(DialogInterface dialog, int which) {
1068 choice.set(which);
1069 }
1070 });
1071 builder.setNegativeButton(R.string.cancel, null);
1072 builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
1073 @Override
1074 public void onClick(DialogInterface dialog, int which) {
1075 prefs.setAttribute("default",defaults.get(choice.get()));
1076 xmppConnectionService.pushMamPreferences(mAccount, prefs);
1077 }
1078 });
1079 builder.create().show();
1080 }
1081 });
1082 }
1083
1084 @Override
1085 public void onPreferencesFetchFailed() {
1086 runOnUiThread(new Runnable() {
1087 @Override
1088 public void run() {
1089 if (mFetchingMamPrefsToast != null) {
1090 mFetchingMamPrefsToast.cancel();
1091 }
1092 Toast.makeText(EditAccountActivity.this,R.string.unable_to_fetch_mam_prefs,Toast.LENGTH_LONG).show();
1093 }
1094 });
1095 }
1096}