1package eu.siacs.conversations.ui;
2
3import android.Manifest;
4import android.annotation.SuppressLint;
5import android.annotation.TargetApi;
6import android.app.ActionBar;
7import android.app.ActionBar.Tab;
8import android.app.ActionBar.TabListener;
9import android.app.AlertDialog;
10import android.app.Dialog;
11import android.app.Fragment;
12import android.app.FragmentManager;
13import android.app.FragmentTransaction;
14import android.app.ListFragment;
15import android.app.PendingIntent;
16import android.content.Context;
17import android.content.DialogInterface;
18import android.content.DialogInterface.OnClickListener;
19import android.content.Intent;
20import android.content.pm.PackageManager;
21import android.net.Uri;
22import android.nfc.NdefMessage;
23import android.nfc.NdefRecord;
24import android.nfc.NfcAdapter;
25import android.os.Build;
26import android.os.Bundle;
27import android.os.Parcelable;
28import android.support.v4.view.PagerAdapter;
29import android.support.v4.view.ViewPager;
30import android.text.Editable;
31import android.text.SpannableString;
32import android.text.Spanned;
33import android.text.TextWatcher;
34import android.text.style.TypefaceSpan;
35import android.util.Pair;
36import android.view.ContextMenu;
37import android.view.ContextMenu.ContextMenuInfo;
38import android.view.KeyEvent;
39import android.view.Menu;
40import android.view.MenuItem;
41import android.view.View;
42import android.view.ViewGroup;
43import android.view.inputmethod.InputMethodManager;
44import android.widget.AdapterView;
45import android.widget.AdapterView.AdapterContextMenuInfo;
46import android.widget.AdapterView.OnItemClickListener;
47import android.widget.ArrayAdapter;
48import android.widget.AutoCompleteTextView;
49import android.widget.CheckBox;
50import android.widget.Checkable;
51import android.widget.EditText;
52import android.widget.ListView;
53import android.widget.Spinner;
54import android.widget.TextView;
55import android.widget.Toast;
56
57import com.google.zxing.integration.android.IntentIntegrator;
58import com.google.zxing.integration.android.IntentResult;
59
60import java.util.ArrayList;
61import java.util.Arrays;
62import java.util.Collections;
63import java.util.List;
64import java.util.concurrent.atomic.AtomicBoolean;
65
66import eu.siacs.conversations.Config;
67import eu.siacs.conversations.R;
68import eu.siacs.conversations.entities.Account;
69import eu.siacs.conversations.entities.Bookmark;
70import eu.siacs.conversations.entities.Contact;
71import eu.siacs.conversations.entities.Conversation;
72import eu.siacs.conversations.entities.ListItem;
73import eu.siacs.conversations.entities.Presence;
74import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
75import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
76import eu.siacs.conversations.ui.adapter.ListItemAdapter;
77import eu.siacs.conversations.utils.XmppUri;
78import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
79import eu.siacs.conversations.xmpp.XmppConnection;
80import eu.siacs.conversations.xmpp.jid.InvalidJidException;
81import eu.siacs.conversations.xmpp.jid.Jid;
82
83public class StartConversationActivity extends XmppActivity implements OnRosterUpdate, OnUpdateBlocklist {
84
85 public int conference_context_id;
86 public int contact_context_id;
87 private Tab mContactsTab;
88 private Tab mConferencesTab;
89 private ViewPager mViewPager;
90 private ListPagerAdapter mListPagerAdapter;
91 private List<ListItem> contacts = new ArrayList<>();
92 private ArrayAdapter<ListItem> mContactsAdapter;
93 private List<ListItem> conferences = new ArrayList<>();
94 private ArrayAdapter<ListItem> mConferenceAdapter;
95 private List<String> mActivatedAccounts = new ArrayList<>();
96 private List<String> mKnownHosts;
97 private List<String> mKnownConferenceHosts;
98 private Invite mPendingInvite = null;
99 private EditText mSearchEditText;
100 private AtomicBoolean mRequestedContactsPermission = new AtomicBoolean(false);
101 private final int REQUEST_SYNC_CONTACTS = 0x3b28cf;
102 private final int REQUEST_CREATE_CONFERENCE = 0x3b39da;
103 private Dialog mCurrentDialog = null;
104
105 private MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
106
107 @Override
108 public boolean onMenuItemActionExpand(MenuItem item) {
109 mSearchEditText.post(new Runnable() {
110
111 @Override
112 public void run() {
113 mSearchEditText.requestFocus();
114 InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
115 imm.showSoftInput(mSearchEditText,
116 InputMethodManager.SHOW_IMPLICIT);
117 }
118 });
119
120 return true;
121 }
122
123 @Override
124 public boolean onMenuItemActionCollapse(MenuItem item) {
125 hideKeyboard();
126 mSearchEditText.setText("");
127 filter(null);
128 return true;
129 }
130 };
131 private boolean mHideOfflineContacts = false;
132 private TabListener mTabListener = new TabListener() {
133
134 @Override
135 public void onTabUnselected(Tab tab, FragmentTransaction ft) {
136 return;
137 }
138
139 @Override
140 public void onTabSelected(Tab tab, FragmentTransaction ft) {
141 mViewPager.setCurrentItem(tab.getPosition());
142 onTabChanged();
143 }
144
145 @Override
146 public void onTabReselected(Tab tab, FragmentTransaction ft) {
147 return;
148 }
149 };
150 private ViewPager.SimpleOnPageChangeListener mOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
151 @Override
152 public void onPageSelected(int position) {
153 if (getActionBar() != null) {
154 getActionBar().setSelectedNavigationItem(position);
155 }
156 onTabChanged();
157 }
158 };
159 private TextWatcher mSearchTextWatcher = new TextWatcher() {
160
161 @Override
162 public void afterTextChanged(Editable editable) {
163 filter(editable.toString());
164 }
165
166 @Override
167 public void beforeTextChanged(CharSequence s, int start, int count,
168 int after) {
169 }
170
171 @Override
172 public void onTextChanged(CharSequence s, int start, int before, int count) {
173 }
174 };
175
176 private TextView.OnEditorActionListener mSearchDone = new TextView.OnEditorActionListener() {
177 @Override
178 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
179 int pos = getActionBar().getSelectedNavigationIndex();
180 if (pos == 0) {
181 if (contacts.size() == 1) {
182 openConversationForContact((Contact) contacts.get(0));
183 return true;
184 }
185 } else {
186 if (conferences.size() == 1) {
187 openConversationsForBookmark((Bookmark) conferences.get(0));
188 return true;
189 }
190 }
191 hideKeyboard();
192 mListPagerAdapter.requestFocus(pos);
193 return true;
194 }
195 };
196 private MenuItem mMenuSearchView;
197 private ListItemAdapter.OnTagClickedListener mOnTagClickedListener = new ListItemAdapter.OnTagClickedListener() {
198 @Override
199 public void onTagClicked(String tag) {
200 if (mMenuSearchView != null) {
201 mMenuSearchView.expandActionView();
202 mSearchEditText.setText("");
203 mSearchEditText.append(tag);
204 filter(tag);
205 }
206 }
207 };
208 private String mInitialJid;
209 private Pair<Integer, Intent> mPostponedActivityResult;
210 private UiCallback<Conversation> mAdhocConferenceCallback = new UiCallback<Conversation>() {
211 @Override
212 public void success(final Conversation conversation) {
213 runOnUiThread(new Runnable() {
214 @Override
215 public void run() {
216 hideToast();
217 switchToConversation(conversation);
218 }
219 });
220 }
221
222 @Override
223 public void error(final int errorCode, Conversation object) {
224 runOnUiThread(new Runnable() {
225 @Override
226 public void run() {
227 replaceToast(getString(errorCode));
228 }
229 });
230 }
231
232 @Override
233 public void userInputRequried(PendingIntent pi, Conversation object) {
234
235 }
236 };
237 private Toast mToast;
238
239 protected void hideToast() {
240 if (mToast != null) {
241 mToast.cancel();
242 }
243 }
244
245 protected void replaceToast(String msg) {
246 hideToast();
247 mToast = Toast.makeText(this, msg, Toast.LENGTH_LONG);
248 mToast.show();
249 }
250
251 @Override
252 public void onRosterUpdate() {
253 this.refreshUi();
254 }
255
256 @Override
257 public void onCreate(Bundle savedInstanceState) {
258 super.onCreate(savedInstanceState);
259 setContentView(R.layout.activity_start_conversation);
260 mViewPager = (ViewPager) findViewById(R.id.start_conversation_view_pager);
261 ActionBar actionBar = getActionBar();
262 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
263
264 mContactsTab = actionBar.newTab().setText(R.string.contacts)
265 .setTabListener(mTabListener);
266 mConferencesTab = actionBar.newTab().setText(R.string.conferences)
267 .setTabListener(mTabListener);
268 actionBar.addTab(mContactsTab);
269 actionBar.addTab(mConferencesTab);
270
271 mViewPager.setOnPageChangeListener(mOnPageChangeListener);
272 mListPagerAdapter = new ListPagerAdapter(getFragmentManager());
273 mViewPager.setAdapter(mListPagerAdapter);
274
275 mConferenceAdapter = new ListItemAdapter(this, conferences);
276 mContactsAdapter = new ListItemAdapter(this, contacts);
277 ((ListItemAdapter) mContactsAdapter).setOnTagClickedListener(this.mOnTagClickedListener);
278 this.mHideOfflineContacts = getPreferences().getBoolean("hide_offline", false);
279
280 }
281
282 @Override
283 public void onStart() {
284 super.onStart();
285 final int theme = findTheme();
286 if (this.mTheme != theme) {
287 recreate();
288 } else {
289 askForContactsPermissions();
290 }
291 }
292
293 @Override
294 public void onStop() {
295 if (mCurrentDialog != null) {
296 mCurrentDialog.dismiss();
297 }
298 super.onStop();
299 }
300
301 protected void openConversationForContact(int position) {
302 Contact contact = (Contact) contacts.get(position);
303 openConversationForContact(contact);
304 }
305
306 protected void openConversationForContact(Contact contact) {
307 Conversation conversation = xmppConnectionService
308 .findOrCreateConversation(contact.getAccount(),
309 contact.getJid(), false);
310 switchToConversation(conversation);
311 }
312
313 protected void openConversationForContact() {
314 int position = contact_context_id;
315 openConversationForContact(position);
316 }
317
318 protected void openConversationForBookmark() {
319 openConversationForBookmark(conference_context_id);
320 }
321
322 protected void openConversationForBookmark(int position) {
323 Bookmark bookmark = (Bookmark) conferences.get(position);
324 openConversationsForBookmark(bookmark);
325 }
326
327 protected void openConversationsForBookmark(Bookmark bookmark) {
328 Jid jid = bookmark.getJid();
329 if (jid == null) {
330 Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
331 return;
332 }
333 Conversation conversation = xmppConnectionService.findOrCreateConversation(bookmark.getAccount(), jid, true);
334 conversation.setBookmark(bookmark);
335 if (!conversation.getMucOptions().online()) {
336 xmppConnectionService.joinMuc(conversation);
337 }
338 if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", true)) {
339 bookmark.setAutojoin(true);
340 xmppConnectionService.pushBookmarks(bookmark.getAccount());
341 }
342 switchToConversation(conversation);
343 }
344
345 protected void openDetailsForContact() {
346 int position = contact_context_id;
347 Contact contact = (Contact) contacts.get(position);
348 switchToContactDetails(contact);
349 }
350
351 protected void toggleContactBlock() {
352 final int position = contact_context_id;
353 BlockContactDialog.show(this, xmppConnectionService, (Contact) contacts.get(position));
354 }
355
356 protected void deleteContact() {
357 final int position = contact_context_id;
358 final Contact contact = (Contact) contacts.get(position);
359 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
360 builder.setNegativeButton(R.string.cancel, null);
361 builder.setTitle(R.string.action_delete_contact);
362 builder.setMessage(getString(R.string.remove_contact_text,
363 contact.getJid()));
364 builder.setPositiveButton(R.string.delete, new OnClickListener() {
365
366 @Override
367 public void onClick(DialogInterface dialog, int which) {
368 xmppConnectionService.deleteContactOnServer(contact);
369 filter(mSearchEditText.getText().toString());
370 }
371 });
372 builder.create().show();
373 }
374
375 protected void deleteConference() {
376 int position = conference_context_id;
377 final Bookmark bookmark = (Bookmark) conferences.get(position);
378
379 AlertDialog.Builder builder = new AlertDialog.Builder(this);
380 builder.setNegativeButton(R.string.cancel, null);
381 builder.setTitle(R.string.delete_bookmark);
382 builder.setMessage(getString(R.string.remove_bookmark_text,
383 bookmark.getJid()));
384 builder.setPositiveButton(R.string.delete, new OnClickListener() {
385
386 @Override
387 public void onClick(DialogInterface dialog, int which) {
388 bookmark.unregisterConversation();
389 Account account = bookmark.getAccount();
390 account.getBookmarks().remove(bookmark);
391 xmppConnectionService.pushBookmarks(account);
392 filter(mSearchEditText.getText().toString());
393 }
394 });
395 builder.create().show();
396
397 }
398
399 @SuppressLint("InflateParams")
400 protected void showCreateContactDialog(final String prefilledJid, final Invite invite) {
401 EnterJidDialog dialog = new EnterJidDialog(
402 this, mKnownHosts, mActivatedAccounts,
403 getString(R.string.create_contact), getString(R.string.create),
404 prefilledJid, null, invite == null || !invite.hasFingerprints()
405 );
406
407 dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() {
408 @Override
409 public boolean onEnterJidDialogPositive(Jid accountJid, Jid contactJid) throws EnterJidDialog.JidError {
410 if (!xmppConnectionServiceBound) {
411 return false;
412 }
413
414 final Account account = xmppConnectionService.findAccountByJid(accountJid);
415 if (account == null) {
416 return true;
417 }
418
419 final Contact contact = account.getRoster().getContact(contactJid);
420 if (contact.showInRoster()) {
421 throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists));
422 } else {
423 xmppConnectionService.createContact(contact);
424 if (invite != null && invite.hasFingerprints()) {
425 xmppConnectionService.verifyFingerprints(contact,invite.getFingerprints());
426 }
427 switchToConversation(contact, invite == null ? null : invite.getBody());
428 return true;
429 }
430 }
431 });
432
433 mCurrentDialog = dialog.show();
434 }
435
436 @SuppressLint("InflateParams")
437 protected void showJoinConferenceDialog(final String prefilledJid) {
438 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
439 builder.setTitle(R.string.join_conference);
440 final View dialogView = getLayoutInflater().inflate(R.layout.join_conference_dialog, null);
441 final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
442 final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid);
443 final TextView jabberIdDesc = (TextView) dialogView.findViewById(R.id.jabber_id);
444 jabberIdDesc.setText(R.string.conference_address);
445 jid.setHint(R.string.conference_address_example);
446 jid.setAdapter(new KnownHostsAdapter(this, R.layout.simple_list_item, mKnownConferenceHosts));
447 if (prefilledJid != null) {
448 jid.append(prefilledJid);
449 }
450 populateAccountSpinner(this, mActivatedAccounts, spinner);
451 final Checkable bookmarkCheckBox = (CheckBox) dialogView
452 .findViewById(R.id.bookmark);
453 builder.setView(dialogView);
454 builder.setNegativeButton(R.string.cancel, null);
455 builder.setPositiveButton(R.string.join, null);
456 final AlertDialog dialog = builder.create();
457 dialog.show();
458 mCurrentDialog = dialog;
459 dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
460 new View.OnClickListener() {
461
462 @Override
463 public void onClick(final View v) {
464 if (!xmppConnectionServiceBound) {
465 return;
466 }
467 final Account account = getSelectedAccount(spinner);
468 if (account == null) {
469 return;
470 }
471 final Jid conferenceJid;
472 try {
473 conferenceJid = Jid.fromString(jid.getText().toString());
474 } catch (final InvalidJidException e) {
475 jid.setError(getString(R.string.invalid_jid));
476 return;
477 }
478
479 if (bookmarkCheckBox.isChecked()) {
480 if (account.hasBookmarkFor(conferenceJid)) {
481 jid.setError(getString(R.string.bookmark_already_exists));
482 } else {
483 final Bookmark bookmark = new Bookmark(account, conferenceJid.toBareJid());
484 bookmark.setAutojoin(getPreferences().getBoolean("autojoin", true));
485 String nick = conferenceJid.getResourcepart();
486 if (nick != null && !nick.isEmpty()) {
487 bookmark.setNick(nick);
488 }
489 account.getBookmarks().add(bookmark);
490 xmppConnectionService.pushBookmarks(account);
491 final Conversation conversation = xmppConnectionService
492 .findOrCreateConversation(account,
493 conferenceJid, true);
494 conversation.setBookmark(bookmark);
495 if (!conversation.getMucOptions().online()) {
496 xmppConnectionService.joinMuc(conversation);
497 }
498 dialog.dismiss();
499 mCurrentDialog = null;
500 switchToConversation(conversation);
501 }
502 } else {
503 final Conversation conversation = xmppConnectionService
504 .findOrCreateConversation(account,
505 conferenceJid, true);
506 if (!conversation.getMucOptions().online()) {
507 xmppConnectionService.joinMuc(conversation);
508 }
509 dialog.dismiss();
510 mCurrentDialog = null;
511 switchToConversation(conversation);
512 }
513 }
514 });
515 }
516
517 private void showCreateConferenceDialog() {
518 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
519 builder.setTitle(R.string.create_conference);
520 final View dialogView = getLayoutInflater().inflate(R.layout.create_conference_dialog, null);
521 final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
522 final EditText subject = (EditText) dialogView.findViewById(R.id.subject);
523 populateAccountSpinner(this, mActivatedAccounts, spinner);
524 builder.setView(dialogView);
525 builder.setPositiveButton(R.string.choose_participants, new OnClickListener() {
526 @Override
527 public void onClick(DialogInterface dialog, int which) {
528 if (!xmppConnectionServiceBound) {
529 return;
530 }
531 final Account account = getSelectedAccount(spinner);
532 if (account == null) {
533 return;
534 }
535 Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class);
536 intent.putExtra("multiple", true);
537 intent.putExtra("show_enter_jid", true);
538 intent.putExtra("subject", subject.getText().toString());
539 intent.putExtra(EXTRA_ACCOUNT, account.getJid().toBareJid().toString());
540 intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants);
541 startActivityForResult(intent, REQUEST_CREATE_CONFERENCE);
542 }
543 });
544 builder.setNegativeButton(R.string.cancel, null);
545 mCurrentDialog = builder.create();
546 mCurrentDialog.show();
547 }
548
549 private Account getSelectedAccount(Spinner spinner) {
550 if (!spinner.isEnabled()) {
551 return null;
552 }
553 Jid jid;
554 try {
555 if (Config.DOMAIN_LOCK != null) {
556 jid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
557 } else {
558 jid = Jid.fromString((String) spinner.getSelectedItem());
559 }
560 } catch (final InvalidJidException e) {
561 return null;
562 }
563 return xmppConnectionService.findAccountByJid(jid);
564 }
565
566 protected void switchToConversation(Contact contact, String body) {
567 Conversation conversation = xmppConnectionService
568 .findOrCreateConversation(contact.getAccount(),
569 contact.getJid(), false);
570 switchToConversation(conversation, body, false);
571 }
572
573 public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) {
574 if (accounts.size() > 0) {
575 ArrayAdapter<String> adapter = new ArrayAdapter<>(context, R.layout.simple_list_item, accounts);
576 adapter.setDropDownViewResource(R.layout.simple_list_item);
577 spinner.setAdapter(adapter);
578 spinner.setEnabled(true);
579 } else {
580 ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
581 R.layout.simple_list_item,
582 Arrays.asList(new String[]{context.getString(R.string.no_accounts)}));
583 adapter.setDropDownViewResource(R.layout.simple_list_item);
584 spinner.setAdapter(adapter);
585 spinner.setEnabled(false);
586 }
587 }
588
589 @Override
590 public boolean onCreateOptionsMenu(Menu menu) {
591 getMenuInflater().inflate(R.menu.start_conversation, menu);
592 MenuItem menuCreateContact = menu.findItem(R.id.action_create_contact);
593 MenuItem menuCreateConference = menu.findItem(R.id.action_conference);
594 MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline);
595 menuHideOffline.setChecked(this.mHideOfflineContacts);
596 mMenuSearchView = menu.findItem(R.id.action_search);
597 mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
598 View mSearchView = mMenuSearchView.getActionView();
599 mSearchEditText = (EditText) mSearchView
600 .findViewById(R.id.search_field);
601 mSearchEditText.addTextChangedListener(mSearchTextWatcher);
602 mSearchEditText.setOnEditorActionListener(mSearchDone);
603 if (getActionBar().getSelectedNavigationIndex() == 0) {
604 menuCreateConference.setVisible(false);
605 } else {
606 menuCreateContact.setVisible(false);
607 }
608 if (mInitialJid != null) {
609 mMenuSearchView.expandActionView();
610 mSearchEditText.append(mInitialJid);
611 filter(mInitialJid);
612 }
613 return super.onCreateOptionsMenu(menu);
614 }
615
616 @Override
617 public boolean onOptionsItemSelected(MenuItem item) {
618 switch (item.getItemId()) {
619 case R.id.action_create_contact:
620 showCreateContactDialog(null, null);
621 return true;
622 case R.id.action_join_conference:
623 showJoinConferenceDialog(null);
624 return true;
625 case R.id.action_create_conference:
626 showCreateConferenceDialog();
627 return true;
628 case R.id.action_scan_qr_code:
629 new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE"));
630 return true;
631 case R.id.action_hide_offline:
632 mHideOfflineContacts = !item.isChecked();
633 getPreferences().edit().putBoolean("hide_offline", mHideOfflineContacts).commit();
634 if (mSearchEditText != null) {
635 filter(mSearchEditText.getText().toString());
636 }
637 invalidateOptionsMenu();
638 }
639 return super.onOptionsItemSelected(item);
640 }
641
642 @Override
643 public boolean onKeyUp(int keyCode, KeyEvent event) {
644 if (keyCode == KeyEvent.KEYCODE_SEARCH && !event.isLongPress()) {
645 openSearch();
646 return true;
647 }
648 int c = event.getUnicodeChar();
649 if (c > 32) {
650 if (mSearchEditText != null && !mSearchEditText.isFocused()) {
651 openSearch();
652 mSearchEditText.append(Character.toString((char) c));
653 return true;
654 }
655 }
656 return super.onKeyUp(keyCode, event);
657 }
658
659 private void openSearch() {
660 if (mMenuSearchView != null) {
661 mMenuSearchView.expandActionView();
662 }
663 }
664
665 @Override
666 public void onActivityResult(int requestCode, int resultCode, Intent intent) {
667 if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) {
668 IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
669 if (scanResult != null && scanResult.getFormatName() != null) {
670 String data = scanResult.getContents();
671 Invite invite = new Invite(data);
672 if (xmppConnectionServiceBound) {
673 invite.invite();
674 } else if (invite.getJid() != null) {
675 this.mPendingInvite = invite;
676 } else {
677 this.mPendingInvite = null;
678 }
679 }
680 } else if (resultCode == RESULT_OK) {
681 if (xmppConnectionServiceBound) {
682 this.mPostponedActivityResult = null;
683 if (requestCode == REQUEST_CREATE_CONFERENCE) {
684 Account account = extractAccount(intent);
685 final String subject = intent.getStringExtra("subject");
686 List<Jid> jids = new ArrayList<>();
687 if (intent.getBooleanExtra("multiple", false)) {
688 String[] toAdd = intent.getStringArrayExtra("contacts");
689 for (String item : toAdd) {
690 try {
691 jids.add(Jid.fromString(item));
692 } catch (InvalidJidException e) {
693 //ignored
694 }
695 }
696 } else {
697 try {
698 jids.add(Jid.fromString(intent.getStringExtra("contact")));
699 } catch (Exception e) {
700 //ignored
701 }
702 }
703 if (account != null && jids.size() > 0) {
704 xmppConnectionService.createAdhocConference(account, subject, jids, mAdhocConferenceCallback);
705 mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
706 mToast.show();
707 }
708 }
709 } else {
710 this.mPostponedActivityResult = new Pair<>(requestCode, intent);
711 }
712 }
713 super.onActivityResult(requestCode, requestCode, intent);
714 }
715
716 private void askForContactsPermissions() {
717 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
718 if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
719 if (mRequestedContactsPermission.compareAndSet(false, true)) {
720 if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
721 AlertDialog.Builder builder = new AlertDialog.Builder(this);
722 builder.setTitle(R.string.sync_with_contacts);
723 builder.setMessage(R.string.sync_with_contacts_long);
724 builder.setPositiveButton(R.string.next, new OnClickListener() {
725 @Override
726 public void onClick(DialogInterface dialog, int which) {
727 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
728 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
729 }
730 }
731 });
732 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
733 builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
734 @Override
735 public void onDismiss(DialogInterface dialog) {
736 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
737 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
738 }
739 }
740 });
741 }
742 builder.create().show();
743 } else {
744 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 0);
745 }
746 }
747 }
748 }
749 }
750
751 @Override
752 public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
753 if (grantResults.length > 0)
754 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
755 if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
756 xmppConnectionService.loadPhoneContacts();
757 }
758 }
759 }
760
761 @Override
762 protected void onBackendConnected() {
763 if (mPostponedActivityResult != null) {
764 onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
765 this.mPostponedActivityResult = null;
766 }
767 this.mActivatedAccounts.clear();
768 for (Account account : xmppConnectionService.getAccounts()) {
769 if (account.getStatus() != Account.State.DISABLED) {
770 if (Config.DOMAIN_LOCK != null) {
771 this.mActivatedAccounts.add(account.getJid().getLocalpart());
772 } else {
773 this.mActivatedAccounts.add(account.getJid().toBareJid().toString());
774 }
775 }
776 }
777 final Intent intent = getIntent();
778 final ActionBar ab = getActionBar();
779 boolean init = intent != null && intent.getBooleanExtra("init", false);
780 boolean noConversations = xmppConnectionService.getConversations().size() == 0;
781 if ((init || noConversations) && ab != null) {
782 ab.setDisplayShowHomeEnabled(false);
783 ab.setDisplayHomeAsUpEnabled(false);
784 ab.setHomeButtonEnabled(false);
785 }
786 this.mKnownHosts = xmppConnectionService.getKnownHosts();
787 this.mKnownConferenceHosts = xmppConnectionService.getKnownConferenceHosts();
788 if (this.mPendingInvite != null) {
789 mPendingInvite.invite();
790 this.mPendingInvite = null;
791 filter(null);
792 } else if (!handleIntent(getIntent())) {
793 if (mSearchEditText != null) {
794 filter(mSearchEditText.getText().toString());
795 } else {
796 filter(null);
797 }
798 } else {
799 filter(null);
800 }
801 setIntent(null);
802 }
803
804 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
805 Invite getInviteJellyBean(NdefRecord record) {
806 return new Invite(record.toUri());
807 }
808
809 protected boolean handleIntent(Intent intent) {
810 if (intent == null || intent.getAction() == null) {
811 return false;
812 }
813 switch (intent.getAction()) {
814 case Intent.ACTION_SENDTO:
815 case Intent.ACTION_VIEW:
816 Uri uri = intent.getData();
817 if (uri != null) {
818 return new Invite(intent.getData(),false).invite();
819 } else {
820 return false;
821 }
822 case NfcAdapter.ACTION_NDEF_DISCOVERED:
823 for (Parcelable message : getIntent().getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
824 if (message instanceof NdefMessage) {
825 for (NdefRecord record : ((NdefMessage) message).getRecords()) {
826 switch (record.getTnf()) {
827 case NdefRecord.TNF_WELL_KNOWN:
828 if (Arrays.equals(record.getType(), NdefRecord.RTD_URI)) {
829 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
830 return getInviteJellyBean(record).invite();
831 } else {
832 byte[] payload = record.getPayload();
833 if (payload[0] == 0) {
834 return new Invite(Uri.parse(new String(Arrays.copyOfRange(
835 payload, 1, payload.length)))).invite();
836 }
837 }
838 }
839 }
840 }
841 }
842 }
843 }
844 return false;
845 }
846
847 private boolean handleJid(Invite invite) {
848 Account account = xmppConnectionService.findAccountByJid(invite.getJid());
849 if (account != null && !account.isOptionSet(Account.OPTION_DISABLED) && invite.hasFingerprints()) {
850 if (xmppConnectionService.verifyFingerprints(account,invite.getFingerprints())) {
851 switchToAccount(account);
852 finish();
853 return true;
854 }
855 }
856 List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid());
857 if (invite.isMuc()) {
858 Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
859 if (muc != null) {
860 switchToConversation(muc,invite.getBody(),false);
861 return true;
862 } else {
863 showJoinConferenceDialog(invite.getJid().toBareJid().toString());
864 return false;
865 }
866 } else if (contacts.size() == 0) {
867 showCreateContactDialog(invite.getJid().toString(), invite);
868 return false;
869 } else if (contacts.size() == 1) {
870 Contact contact = contacts.get(0);
871 if (!invite.isSafeSource() && invite.hasFingerprints()) {
872 displayVerificationWarningDialog(contact,invite);
873 } else {
874 if (invite.hasFingerprints()) {
875 xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
876 }
877 switchToConversation(contact, invite.getBody());
878 }
879 return true;
880 } else {
881 if (mMenuSearchView != null) {
882 mMenuSearchView.expandActionView();
883 mSearchEditText.setText("");
884 mSearchEditText.append(invite.getJid().toString());
885 filter(invite.getJid().toString());
886 } else {
887 mInitialJid = invite.getJid().toString();
888 }
889 return true;
890 }
891 }
892
893 private void displayVerificationWarningDialog(final Contact contact, final Invite invite) {
894 AlertDialog.Builder builder = new AlertDialog.Builder(this);
895 builder.setTitle(R.string.verify_omemo_keys);
896 View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
897 final CheckBox isTrustedSource = (CheckBox) view.findViewById(R.id.trusted_source);
898 TextView warning = (TextView) view.findViewById(R.id.warning);
899 String jid = contact.getJid().toBareJid().toString();
900 SpannableString spannable = new SpannableString(getString(R.string.verifying_omemo_keys_trusted_source,jid,contact.getDisplayName()));
901 int start = spannable.toString().indexOf(jid);
902 if (start >= 0) {
903 spannable.setSpan(new TypefaceSpan("monospace"),start,start + jid.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
904 }
905 warning.setText(spannable);
906 builder.setView(view);
907 builder.setPositiveButton(R.string.confirm, new OnClickListener() {
908 @Override
909 public void onClick(DialogInterface dialog, int which) {
910 if (isTrustedSource.isChecked() && invite.hasFingerprints()) {
911 xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
912 }
913 switchToConversation(contact, invite.getBody());
914 }
915 });
916 builder.setNegativeButton(R.string.cancel, new OnClickListener() {
917 @Override
918 public void onClick(DialogInterface dialog, int which) {
919 StartConversationActivity.this.finish();
920 }
921 });
922 AlertDialog dialog = builder.create();
923 dialog.setCanceledOnTouchOutside(false);
924 dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
925 @Override
926 public void onCancel(DialogInterface dialog) {
927 StartConversationActivity.this.finish();
928 }
929 });
930 dialog.show();
931 }
932
933 protected void filter(String needle) {
934 if (xmppConnectionServiceBound) {
935 this.filterContacts(needle);
936 this.filterConferences(needle);
937 }
938 }
939
940 protected void filterContacts(String needle) {
941 this.contacts.clear();
942 for (Account account : xmppConnectionService.getAccounts()) {
943 if (account.getStatus() != Account.State.DISABLED) {
944 for (Contact contact : account.getRoster().getContacts()) {
945 Presence.Status s = contact.getShownStatus();
946 if (contact.showInRoster() && contact.match(this, needle)
947 && (!this.mHideOfflineContacts
948 || (needle != null && !needle.trim().isEmpty())
949 || s.compareTo(Presence.Status.OFFLINE) < 0)) {
950 this.contacts.add(contact);
951 }
952 }
953 }
954 }
955 Collections.sort(this.contacts);
956 mContactsAdapter.notifyDataSetChanged();
957 }
958
959 protected void filterConferences(String needle) {
960 this.conferences.clear();
961 for (Account account : xmppConnectionService.getAccounts()) {
962 if (account.getStatus() != Account.State.DISABLED) {
963 for (Bookmark bookmark : account.getBookmarks()) {
964 if (bookmark.match(this, needle)) {
965 this.conferences.add(bookmark);
966 }
967 }
968 }
969 }
970 Collections.sort(this.conferences);
971 mConferenceAdapter.notifyDataSetChanged();
972 }
973
974 private void onTabChanged() {
975 invalidateOptionsMenu();
976 }
977
978 @Override
979 public void OnUpdateBlocklist(final Status status) {
980 refreshUi();
981 }
982
983 @Override
984 protected void refreshUiReal() {
985 if (mSearchEditText != null) {
986 filter(mSearchEditText.getText().toString());
987 }
988 }
989
990 public class ListPagerAdapter extends PagerAdapter {
991 FragmentManager fragmentManager;
992 MyListFragment[] fragments;
993
994 public ListPagerAdapter(FragmentManager fm) {
995 fragmentManager = fm;
996 fragments = new MyListFragment[2];
997 }
998
999 public void requestFocus(int pos) {
1000 if (fragments.length > pos) {
1001 fragments[pos].getListView().requestFocus();
1002 }
1003 }
1004
1005 @Override
1006 public void destroyItem(ViewGroup container, int position, Object object) {
1007 assert (0 <= position && position < fragments.length);
1008 FragmentTransaction trans = fragmentManager.beginTransaction();
1009 trans.remove(fragments[position]);
1010 trans.commit();
1011 fragments[position] = null;
1012 }
1013
1014 @Override
1015 public Fragment instantiateItem(ViewGroup container, int position) {
1016 Fragment fragment = getItem(position);
1017 FragmentTransaction trans = fragmentManager.beginTransaction();
1018 trans.add(container.getId(), fragment, "fragment:" + position);
1019 trans.commit();
1020 return fragment;
1021 }
1022
1023 @Override
1024 public int getCount() {
1025 return fragments.length;
1026 }
1027
1028 @Override
1029 public boolean isViewFromObject(View view, Object fragment) {
1030 return ((Fragment) fragment).getView() == view;
1031 }
1032
1033 public Fragment getItem(int position) {
1034 assert (0 <= position && position < fragments.length);
1035 if (fragments[position] == null) {
1036 final MyListFragment listFragment = new MyListFragment();
1037 if (position == 1) {
1038 listFragment.setListAdapter(mConferenceAdapter);
1039 listFragment.setContextMenu(R.menu.conference_context);
1040 listFragment.setOnListItemClickListener(new OnItemClickListener() {
1041
1042 @Override
1043 public void onItemClick(AdapterView<?> arg0, View arg1,
1044 int position, long arg3) {
1045 openConversationForBookmark(position);
1046 }
1047 });
1048 } else {
1049
1050 listFragment.setListAdapter(mContactsAdapter);
1051 listFragment.setContextMenu(R.menu.contact_context);
1052 listFragment.setOnListItemClickListener(new OnItemClickListener() {
1053
1054 @Override
1055 public void onItemClick(AdapterView<?> arg0, View arg1,
1056 int position, long arg3) {
1057 openConversationForContact(position);
1058 }
1059 });
1060 }
1061 fragments[position] = listFragment;
1062 }
1063 return fragments[position];
1064 }
1065 }
1066
1067 public static class MyListFragment extends ListFragment {
1068 private AdapterView.OnItemClickListener mOnItemClickListener;
1069 private int mResContextMenu;
1070
1071 public void setContextMenu(final int res) {
1072 this.mResContextMenu = res;
1073 }
1074
1075 @Override
1076 public void onListItemClick(final ListView l, final View v, final int position, final long id) {
1077 if (mOnItemClickListener != null) {
1078 mOnItemClickListener.onItemClick(l, v, position, id);
1079 }
1080 }
1081
1082 public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
1083 this.mOnItemClickListener = l;
1084 }
1085
1086 @Override
1087 public void onViewCreated(final View view, final Bundle savedInstanceState) {
1088 super.onViewCreated(view, savedInstanceState);
1089 registerForContextMenu(getListView());
1090 getListView().setFastScrollEnabled(true);
1091 }
1092
1093 @Override
1094 public void onCreateContextMenu(final ContextMenu menu, final View v,
1095 final ContextMenuInfo menuInfo) {
1096 super.onCreateContextMenu(menu, v, menuInfo);
1097 final StartConversationActivity activity = (StartConversationActivity) getActivity();
1098 activity.getMenuInflater().inflate(mResContextMenu, menu);
1099 final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
1100 if (mResContextMenu == R.menu.conference_context) {
1101 activity.conference_context_id = acmi.position;
1102 } else if (mResContextMenu == R.menu.contact_context) {
1103 activity.contact_context_id = acmi.position;
1104 final Contact contact = (Contact) activity.contacts.get(acmi.position);
1105 final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
1106 final MenuItem showContactDetailsItem = menu.findItem(R.id.context_contact_details);
1107 if (contact.isSelf()) {
1108 showContactDetailsItem.setVisible(false);
1109 }
1110 XmppConnection xmpp = contact.getAccount().getXmppConnection();
1111 if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) {
1112 if (contact.isBlocked()) {
1113 blockUnblockItem.setTitle(R.string.unblock_contact);
1114 } else {
1115 blockUnblockItem.setTitle(R.string.block_contact);
1116 }
1117 } else {
1118 blockUnblockItem.setVisible(false);
1119 }
1120 }
1121 }
1122
1123 @Override
1124 public boolean onContextItemSelected(final MenuItem item) {
1125 StartConversationActivity activity = (StartConversationActivity) getActivity();
1126 switch (item.getItemId()) {
1127 case R.id.context_start_conversation:
1128 activity.openConversationForContact();
1129 break;
1130 case R.id.context_contact_details:
1131 activity.openDetailsForContact();
1132 break;
1133 case R.id.context_contact_block_unblock:
1134 activity.toggleContactBlock();
1135 break;
1136 case R.id.context_delete_contact:
1137 activity.deleteContact();
1138 break;
1139 case R.id.context_join_conference:
1140 activity.openConversationForBookmark();
1141 break;
1142 case R.id.context_delete_conference:
1143 activity.deleteConference();
1144 }
1145 return true;
1146 }
1147 }
1148
1149 private class Invite extends XmppUri {
1150
1151 public Invite(final Uri uri) {
1152 super(uri);
1153 }
1154
1155 public Invite(final String uri) {
1156 super(uri);
1157 }
1158
1159 public Invite(Uri uri, boolean safeSource) {
1160 super(uri,safeSource);
1161 }
1162
1163 boolean invite() {
1164 if (getJid() != null) {
1165 return handleJid(this);
1166 }
1167 return false;
1168 }
1169
1170 public boolean isMuc() {
1171 return muc;
1172 }
1173 }
1174}