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