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