StartConversationActivity.java

  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}