1package eu.siacs.conversations.ui;
2
3import java.io.UnsupportedEncodingException;
4import java.net.MalformedURLException;
5import java.net.URL;
6import java.net.URLDecoder;
7import java.util.ArrayList;
8import java.util.Collections;
9import java.util.List;
10
11import android.annotation.SuppressLint;
12import android.app.ActionBar;
13import android.app.ActionBar.Tab;
14import android.app.ActionBar.TabListener;
15import android.app.AlertDialog;
16import android.app.Fragment;
17import android.app.FragmentTransaction;
18import android.app.ListFragment;
19import android.content.Context;
20import android.content.DialogInterface;
21import android.content.DialogInterface.OnClickListener;
22import android.content.Intent;
23import android.net.Uri;
24import android.nfc.NdefMessage;
25import android.nfc.NfcAdapter;
26import android.os.Bundle;
27import android.os.Parcelable;
28import android.support.v13.app.FragmentPagerAdapter;
29import android.support.v4.view.ViewPager;
30import android.text.Editable;
31import android.text.TextWatcher;
32import android.util.Log;
33import android.view.ContextMenu;
34import android.view.ContextMenu.ContextMenuInfo;
35import android.view.KeyEvent;
36import android.view.Menu;
37import android.view.MenuItem;
38import android.view.View;
39import android.view.inputmethod.InputMethodManager;
40import android.widget.AdapterView;
41import android.widget.AdapterView.AdapterContextMenuInfo;
42import android.widget.AdapterView.OnItemClickListener;
43import android.widget.ArrayAdapter;
44import android.widget.AutoCompleteTextView;
45import android.widget.CheckBox;
46import android.widget.EditText;
47import android.widget.ListView;
48import android.widget.Spinner;
49
50import eu.siacs.conversations.Config;
51import eu.siacs.conversations.R;
52import eu.siacs.conversations.entities.Account;
53import eu.siacs.conversations.entities.Bookmark;
54import eu.siacs.conversations.entities.Contact;
55import eu.siacs.conversations.entities.Conversation;
56import eu.siacs.conversations.entities.ListItem;
57import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
58import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
59import eu.siacs.conversations.ui.adapter.ListItemAdapter;
60import eu.siacs.conversations.utils.Validator;
61
62public class StartConversationActivity extends XmppActivity {
63
64 private Tab mContactsTab;
65 private Tab mConferencesTab;
66 private ViewPager mViewPager;
67
68 private MyListFragment mContactsListFragment = new MyListFragment();
69 private List<ListItem> contacts = new ArrayList<ListItem>();
70 private ArrayAdapter<ListItem> mContactsAdapter;
71
72 private MyListFragment mConferenceListFragment = new MyListFragment();
73 private List<ListItem> conferences = new ArrayList<ListItem>();
74 private ArrayAdapter<ListItem> mConferenceAdapter;
75
76 private List<String> mActivatedAccounts = new ArrayList<String>();
77 private List<String> mKnownHosts;
78 private List<String> mKnownConferenceHosts;
79
80 private Menu mOptionsMenu;
81 private EditText mSearchEditText;
82
83 public int conference_context_id;
84 public int contact_context_id;
85
86 private TabListener mTabListener = new TabListener() {
87
88 @Override
89 public void onTabUnselected(Tab tab, FragmentTransaction ft) {
90 return;
91 }
92
93 @Override
94 public void onTabSelected(Tab tab, FragmentTransaction ft) {
95 mViewPager.setCurrentItem(tab.getPosition());
96 onTabChanged();
97 }
98
99 @Override
100 public void onTabReselected(Tab tab, FragmentTransaction ft) {
101 return;
102 }
103 };
104
105 private ViewPager.SimpleOnPageChangeListener mOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
106 @Override
107 public void onPageSelected(int position) {
108 getActionBar().setSelectedNavigationItem(position);
109 onTabChanged();
110 }
111 };
112
113 private MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
114
115 @Override
116 public boolean onMenuItemActionExpand(MenuItem item) {
117 mSearchEditText.post(new Runnable() {
118
119 @Override
120 public void run() {
121 mSearchEditText.requestFocus();
122 InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
123 imm.showSoftInput(mSearchEditText,
124 InputMethodManager.SHOW_IMPLICIT);
125 }
126 });
127
128 return true;
129 }
130
131 @Override
132 public boolean onMenuItemActionCollapse(MenuItem item) {
133 InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
134 imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(),
135 InputMethodManager.HIDE_IMPLICIT_ONLY);
136 mSearchEditText.setText("");
137 filter(null);
138 return true;
139 }
140 };
141 private TextWatcher mSearchTextWatcher = new TextWatcher() {
142
143 @Override
144 public void afterTextChanged(Editable editable) {
145 filter(editable.toString());
146 }
147
148 @Override
149 public void beforeTextChanged(CharSequence s, int start, int count,
150 int after) {
151 }
152
153 @Override
154 public void onTextChanged(CharSequence s, int start, int before,
155 int count) {
156 }
157 };
158 private OnRosterUpdate onRosterUpdate = new OnRosterUpdate() {
159
160 @Override
161 public void onRosterUpdate() {
162 runOnUiThread(new Runnable() {
163
164 @Override
165 public void run() {
166 if (mSearchEditText != null) {
167 filter(mSearchEditText.getText().toString());
168 }
169 }
170 });
171 }
172 };
173 private MenuItem mMenuSearchView;
174 private String mInitialJid;
175
176 @Override
177 public void onCreate(Bundle savedInstanceState) {
178 super.onCreate(savedInstanceState);
179 setContentView(R.layout.activity_start_conversation);
180 mViewPager = (ViewPager) findViewById(R.id.start_conversation_view_pager);
181 ActionBar actionBar = getActionBar();
182 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
183
184 mContactsTab = actionBar.newTab().setText(R.string.contacts)
185 .setTabListener(mTabListener);
186 mConferencesTab = actionBar.newTab().setText(R.string.conferences)
187 .setTabListener(mTabListener);
188 actionBar.addTab(mContactsTab);
189 actionBar.addTab(mConferencesTab);
190
191 mViewPager.setOnPageChangeListener(mOnPageChangeListener);
192 mViewPager.setAdapter(new FragmentPagerAdapter(getFragmentManager()) {
193
194 @Override
195 public int getCount() {
196 return 2;
197 }
198
199 @Override
200 public Fragment getItem(int position) {
201 if (position == 0) {
202 return mContactsListFragment;
203 } else {
204 return mConferenceListFragment;
205 }
206 }
207 });
208
209 mConferenceAdapter = new ListItemAdapter(this, conferences);
210 mConferenceListFragment.setListAdapter(mConferenceAdapter);
211 mConferenceListFragment.setContextMenu(R.menu.conference_context);
212 mConferenceListFragment
213 .setOnListItemClickListener(new OnItemClickListener() {
214
215 @Override
216 public void onItemClick(AdapterView<?> arg0, View arg1,
217 int position, long arg3) {
218 openConversationForBookmark(position);
219 }
220 });
221
222 mContactsAdapter = new ListItemAdapter(this, contacts);
223 mContactsListFragment.setListAdapter(mContactsAdapter);
224 mContactsListFragment.setContextMenu(R.menu.contact_context);
225 mContactsListFragment
226 .setOnListItemClickListener(new OnItemClickListener() {
227
228 @Override
229 public void onItemClick(AdapterView<?> arg0, View arg1,
230 int position, long arg3) {
231 openConversationForContact(position);
232 }
233 });
234
235 }
236
237 @Override
238 public void onStop() {
239 super.onStop();
240 xmppConnectionService.removeOnRosterUpdateListener();
241 }
242
243 protected void openConversationForContact(int position) {
244 Contact contact = (Contact) contacts.get(position);
245 Conversation conversation = xmppConnectionService
246 .findOrCreateConversation(contact.getAccount(),
247 contact.getJid(), false);
248 switchToConversation(conversation);
249 }
250
251 protected void openConversationForContact() {
252 int position = contact_context_id;
253 openConversationForContact(position);
254 }
255
256 protected void openConversationForBookmark() {
257 openConversationForBookmark(conference_context_id);
258 }
259
260 protected void openConversationForBookmark(int position) {
261 Bookmark bookmark = (Bookmark) conferences.get(position);
262 Conversation conversation = xmppConnectionService
263 .findOrCreateConversation(bookmark.getAccount(),
264 bookmark.getJid(), true);
265 conversation.setBookmark(bookmark);
266 if (!conversation.getMucOptions().online()) {
267 xmppConnectionService.joinMuc(conversation);
268 }
269 if (!bookmark.autojoin()) {
270 bookmark.setAutojoin(true);
271 xmppConnectionService.pushBookmarks(bookmark.getAccount());
272 }
273 switchToConversation(conversation);
274 }
275
276 protected void openDetailsForContact() {
277 int position = contact_context_id;
278 Contact contact = (Contact) contacts.get(position);
279 switchToContactDetails(contact);
280 }
281
282 protected void deleteContact() {
283 int position = contact_context_id;
284 final Contact contact = (Contact) contacts.get(position);
285 AlertDialog.Builder builder = new AlertDialog.Builder(this);
286 builder.setNegativeButton(R.string.cancel, null);
287 builder.setTitle(R.string.action_delete_contact);
288 builder.setMessage(getString(R.string.remove_contact_text,
289 contact.getJid()));
290 builder.setPositiveButton(R.string.delete, new OnClickListener() {
291
292 @Override
293 public void onClick(DialogInterface dialog, int which) {
294 xmppConnectionService.deleteContactOnServer(contact);
295 filter(mSearchEditText.getText().toString());
296 }
297 });
298 builder.create().show();
299
300 }
301
302 protected void deleteConference() {
303 int position = conference_context_id;
304 final Bookmark bookmark = (Bookmark) conferences.get(position);
305
306 AlertDialog.Builder builder = new AlertDialog.Builder(this);
307 builder.setNegativeButton(R.string.cancel, null);
308 builder.setTitle(R.string.delete_bookmark);
309 builder.setMessage(getString(R.string.remove_bookmark_text,
310 bookmark.getJid()));
311 builder.setPositiveButton(R.string.delete, new OnClickListener() {
312
313 @Override
314 public void onClick(DialogInterface dialog, int which) {
315 bookmark.unregisterConversation();
316 Account account = bookmark.getAccount();
317 account.getBookmarks().remove(bookmark);
318 xmppConnectionService.pushBookmarks(account);
319 filter(mSearchEditText.getText().toString());
320 }
321 });
322 builder.create().show();
323
324 }
325
326 @SuppressLint("InflateParams")
327 protected void showCreateContactDialog(String prefilledJid) {
328 AlertDialog.Builder builder = new AlertDialog.Builder(this);
329 builder.setTitle(R.string.create_contact);
330 View dialogView = getLayoutInflater().inflate(
331 R.layout.create_contact_dialog, null);
332 final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
333 final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView
334 .findViewById(R.id.jid);
335 jid.setAdapter(new KnownHostsAdapter(this,
336 android.R.layout.simple_list_item_1, mKnownHosts));
337 if (prefilledJid != null) {
338 jid.append(prefilledJid);
339 }
340 populateAccountSpinner(spinner);
341 builder.setView(dialogView);
342 builder.setNegativeButton(R.string.cancel, null);
343 builder.setPositiveButton(R.string.create, null);
344 final AlertDialog dialog = builder.create();
345 dialog.show();
346 dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
347 new View.OnClickListener() {
348
349 @Override
350 public void onClick(View v) {
351 if (!xmppConnectionServiceBound) {
352 return;
353 }
354 if (Validator.isValidJid(jid.getText().toString())) {
355 String accountJid = (String) spinner
356 .getSelectedItem();
357 String contactJid = jid.getText().toString();
358 Account account = xmppConnectionService
359 .findAccountByJid(accountJid);
360 if (account == null) {
361 dialog.dismiss();
362 return;
363 }
364 Contact contact = account.getRoster().getContact(
365 contactJid);
366 if (contact.showInRoster()) {
367 jid.setError(getString(R.string.contact_already_exists));
368 } else {
369 xmppConnectionService.createContact(contact);
370 dialog.dismiss();
371 switchToConversation(contact);
372 }
373 } else {
374 jid.setError(getString(R.string.invalid_jid));
375 }
376 }
377 });
378
379 }
380
381 @SuppressLint("InflateParams")
382 protected void showJoinConferenceDialog(String prefilledJid) {
383 AlertDialog.Builder builder = new AlertDialog.Builder(this);
384 builder.setTitle(R.string.join_conference);
385 View dialogView = getLayoutInflater().inflate(
386 R.layout.join_conference_dialog, null);
387 final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
388 final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView
389 .findViewById(R.id.jid);
390 jid.setAdapter(new KnownHostsAdapter(this,
391 android.R.layout.simple_list_item_1, mKnownConferenceHosts));
392 if (prefilledJid != null) {
393 jid.append(prefilledJid);
394 }
395 populateAccountSpinner(spinner);
396 final CheckBox bookmarkCheckBox = (CheckBox) dialogView
397 .findViewById(R.id.bookmark);
398 builder.setView(dialogView);
399 builder.setNegativeButton(R.string.cancel, null);
400 builder.setPositiveButton(R.string.join, null);
401 final AlertDialog dialog = builder.create();
402 dialog.show();
403 dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
404 new View.OnClickListener() {
405
406 @Override
407 public void onClick(View v) {
408 if (!xmppConnectionServiceBound) {
409 return;
410 }
411 if (Validator.isValidJid(jid.getText().toString())) {
412 String accountJid = (String) spinner
413 .getSelectedItem();
414 String conferenceJid = jid.getText().toString();
415 Account account = xmppConnectionService
416 .findAccountByJid(accountJid);
417 if (account == null) {
418 dialog.dismiss();
419 return;
420 }
421 if (bookmarkCheckBox.isChecked()) {
422 if (account.hasBookmarkFor(conferenceJid)) {
423 jid.setError(getString(R.string.bookmark_already_exists));
424 } else {
425 Bookmark bookmark = new Bookmark(account,
426 conferenceJid);
427 bookmark.setAutojoin(true);
428 account.getBookmarks().add(bookmark);
429 xmppConnectionService
430 .pushBookmarks(account);
431 Conversation conversation = xmppConnectionService
432 .findOrCreateConversation(account,
433 conferenceJid, true);
434 conversation.setBookmark(bookmark);
435 if (!conversation.getMucOptions().online()) {
436 xmppConnectionService
437 .joinMuc(conversation);
438 }
439 dialog.dismiss();
440 switchToConversation(conversation);
441 }
442 } else {
443 Conversation conversation = xmppConnectionService
444 .findOrCreateConversation(account,
445 conferenceJid, true);
446 if (!conversation.getMucOptions().online()) {
447 xmppConnectionService.joinMuc(conversation);
448 }
449 dialog.dismiss();
450 switchToConversation(conversation);
451 }
452 } else {
453 jid.setError(getString(R.string.invalid_jid));
454 }
455 }
456 });
457 }
458
459 protected void switchToConversation(Contact contact) {
460 Conversation conversation = xmppConnectionService
461 .findOrCreateConversation(contact.getAccount(),
462 contact.getJid(), false);
463 switchToConversation(conversation);
464 }
465
466 private void populateAccountSpinner(Spinner spinner) {
467 ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
468 android.R.layout.simple_spinner_item, mActivatedAccounts);
469 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
470 spinner.setAdapter(adapter);
471 }
472
473 @Override
474 public boolean onCreateOptionsMenu(Menu menu) {
475 this.mOptionsMenu = menu;
476 getMenuInflater().inflate(R.menu.start_conversation, menu);
477 MenuItem menuCreateContact = menu
478 .findItem(R.id.action_create_contact);
479 MenuItem menuCreateConference = menu
480 .findItem(R.id.action_join_conference);
481 mMenuSearchView = menu.findItem(R.id.action_search);
482 mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
483 View mSearchView = mMenuSearchView.getActionView();
484 mSearchEditText = (EditText) mSearchView
485 .findViewById(R.id.search_field);
486 mSearchEditText.addTextChangedListener(mSearchTextWatcher);
487 if (getActionBar().getSelectedNavigationIndex() == 0) {
488 menuCreateConference.setVisible(false);
489 } else {
490 menuCreateContact.setVisible(false);
491 }
492 if (mInitialJid != null) {
493 mMenuSearchView.expandActionView();
494 mSearchEditText.append(mInitialJid);
495 filter(mInitialJid);
496 }
497 return true;
498 }
499
500 @Override
501 public boolean onOptionsItemSelected(MenuItem item) {
502 switch (item.getItemId()) {
503 case R.id.action_create_contact:
504 showCreateContactDialog(null);
505 break;
506 case R.id.action_join_conference:
507 showJoinConferenceDialog(null);
508 break;
509 }
510 return super.onOptionsItemSelected(item);
511 }
512
513 @Override
514 public boolean onKeyUp(int keyCode, KeyEvent event) {
515 if (keyCode == KeyEvent.KEYCODE_SEARCH && !event.isLongPress()) {
516 mOptionsMenu.findItem(R.id.action_search).expandActionView();
517 return true;
518 }
519 return super.onKeyUp(keyCode, event);
520 }
521
522 @Override
523 protected void onBackendConnected() {
524 xmppConnectionService.setOnRosterUpdateListener(this.onRosterUpdate);
525 this.mActivatedAccounts.clear();
526 for (Account account : xmppConnectionService.getAccounts()) {
527 if (account.getStatus() != Account.STATUS_DISABLED) {
528 this.mActivatedAccounts.add(account.getJid());
529 }
530 }
531 this.mKnownHosts = xmppConnectionService.getKnownHosts();
532 this.mKnownConferenceHosts = xmppConnectionService
533 .getKnownConferenceHosts();
534 if (!handleIntent(getIntent())) {
535 if (mSearchEditText != null) {
536 filter(mSearchEditText.getText().toString());
537 } else {
538 filter(null);
539 }
540 }
541 }
542
543 protected boolean handleIntent(Intent intent) {
544 if (intent==null || intent.getAction() == null) {
545 return false;
546 }
547 String jid;
548 switch(intent.getAction()) {
549 case Intent.ACTION_SENDTO:
550 try {
551 jid = URLDecoder.decode(
552 intent.getData().getEncodedPath(), "UTF-8").split(
553 "/")[1];
554 return handleJid(jid);
555 } catch (UnsupportedEncodingException e) {
556 return false;
557 }
558 case Intent.ACTION_VIEW:
559 Uri uri = intent.getData();
560 boolean muc = uri.getQuery() != null && uri.getQuery().equalsIgnoreCase("join");
561 if (uri.getAuthority() != null) {
562 jid = uri.getAuthority();
563 } else {
564 jid = uri.getSchemeSpecificPart().split("\\?")[0];
565 }
566 if (muc) {
567 showJoinConferenceDialog(jid);
568 return false;
569 } else {
570 return handleJid(jid);
571 }
572 case NfcAdapter.ACTION_NDEF_DISCOVERED:
573 Parcelable[] messages = getIntent().getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
574 NdefMessage message = (NdefMessage) messages[0];
575 String payload = message.getRecords()[0].toString();
576 if (payload.startsWith("xmpp:")) {
577 jid = payload.substring(5);
578 return handleJid(jid);
579 } else {
580 return false;
581 }
582 default:
583 return false;
584 }
585 }
586
587 private boolean handleJid(String jid) {
588 List<Contact> contacts = xmppConnectionService.findContacts(jid);
589 if (contacts.size() == 0) {
590 showCreateContactDialog(jid);
591 return false;
592 } else if (contacts.size() == 1) {
593 switchToConversation(contacts.get(0));
594 return true;
595 } else {
596 if (mMenuSearchView != null) {
597 mMenuSearchView.expandActionView();
598 mSearchEditText.setText(jid);
599 filter(jid);
600 } else {
601 mInitialJid = jid;
602 }
603 return true;
604 }
605 }
606
607 protected void filter(String needle) {
608 if (xmppConnectionServiceBound) {
609 this.filterContacts(needle);
610 this.filterConferences(needle);
611 }
612 }
613
614 protected void filterContacts(String needle) {
615 this.contacts.clear();
616 for (Account account : xmppConnectionService.getAccounts()) {
617 if (account.getStatus() != Account.STATUS_DISABLED) {
618 for (Contact contact : account.getRoster().getContacts()) {
619 if (contact.showInRoster() && contact.match(needle)) {
620 this.contacts.add(contact);
621 }
622 }
623 }
624 }
625 Collections.sort(this.contacts);
626 mContactsAdapter.notifyDataSetChanged();
627 }
628
629 protected void filterConferences(String needle) {
630 this.conferences.clear();
631 for (Account account : xmppConnectionService.getAccounts()) {
632 if (account.getStatus() != Account.STATUS_DISABLED) {
633 for (Bookmark bookmark : account.getBookmarks()) {
634 if (bookmark.match(needle)) {
635 this.conferences.add(bookmark);
636 }
637 }
638 }
639 }
640 Collections.sort(this.conferences);
641 mConferenceAdapter.notifyDataSetChanged();
642 }
643
644 private void onTabChanged() {
645 invalidateOptionsMenu();
646 }
647
648 public static class MyListFragment extends ListFragment {
649 private AdapterView.OnItemClickListener mOnItemClickListener;
650 private int mResContextMenu;
651
652 public void setContextMenu(int res) {
653 this.mResContextMenu = res;
654 }
655
656 @Override
657 public void onListItemClick(ListView l, View v, int position, long id) {
658 if (mOnItemClickListener != null) {
659 mOnItemClickListener.onItemClick(l, v, position, id);
660 }
661 }
662
663 public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
664 this.mOnItemClickListener = l;
665 }
666
667 @Override
668 public void onViewCreated(View view, Bundle savedInstanceState) {
669 super.onViewCreated(view, savedInstanceState);
670 registerForContextMenu(getListView());
671 getListView().setFastScrollEnabled(true);
672 }
673
674 @Override
675 public void onCreateContextMenu(ContextMenu menu, View v,
676 ContextMenuInfo menuInfo) {
677 super.onCreateContextMenu(menu, v, menuInfo);
678 StartConversationActivity activity = (StartConversationActivity) getActivity();
679 activity.getMenuInflater().inflate(mResContextMenu, menu);
680 AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
681 if (mResContextMenu == R.menu.conference_context) {
682 activity.conference_context_id = acmi.position;
683 } else {
684 activity.contact_context_id = acmi.position;
685 }
686 }
687
688 @Override
689 public boolean onContextItemSelected(MenuItem item) {
690 StartConversationActivity activity = (StartConversationActivity) getActivity();
691 switch (item.getItemId()) {
692 case R.id.context_start_conversation:
693 activity.openConversationForContact();
694 break;
695 case R.id.context_contact_details:
696 activity.openDetailsForContact();
697 break;
698 case R.id.context_delete_contact:
699 activity.deleteContact();
700 break;
701 case R.id.context_join_conference:
702 activity.openConversationForBookmark();
703 break;
704 case R.id.context_delete_conference:
705 activity.deleteConference();
706 }
707 return true;
708 }
709 }
710}