StartConversationActivity.java

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