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