StartConversationActivity.java

   1package eu.siacs.conversations.ui;
   2
   3import android.Manifest;
   4import android.annotation.SuppressLint;
   5import android.annotation.TargetApi;
   6import android.app.ActionBar;
   7import android.app.ActionBar.Tab;
   8import android.app.ActionBar.TabListener;
   9import android.app.AlertDialog;
  10import android.app.Fragment;
  11import android.app.FragmentTransaction;
  12import android.app.ListFragment;
  13import android.app.PendingIntent;
  14import android.content.Context;
  15import android.content.DialogInterface;
  16import android.content.DialogInterface.OnClickListener;
  17import android.content.Intent;
  18import android.content.pm.PackageManager;
  19import android.net.Uri;
  20import android.nfc.NdefMessage;
  21import android.nfc.NdefRecord;
  22import android.nfc.NfcAdapter;
  23import android.os.Build;
  24import android.os.Bundle;
  25import android.os.Parcelable;
  26import android.support.v13.app.FragmentPagerAdapter;
  27import android.support.v4.view.ViewPager;
  28import android.text.Editable;
  29import android.text.TextWatcher;
  30import android.util.Log;
  31import android.util.Pair;
  32import android.view.ContextMenu;
  33import android.view.ContextMenu.ContextMenuInfo;
  34import android.view.KeyEvent;
  35import android.view.Menu;
  36import android.view.MenuItem;
  37import android.view.View;
  38import android.view.inputmethod.InputMethodManager;
  39import android.widget.AdapterView;
  40import android.widget.AdapterView.AdapterContextMenuInfo;
  41import android.widget.AdapterView.OnItemClickListener;
  42import android.widget.ArrayAdapter;
  43import android.widget.AutoCompleteTextView;
  44import android.widget.CheckBox;
  45import android.widget.Checkable;
  46import android.widget.EditText;
  47import android.widget.ListView;
  48import android.widget.Spinner;
  49import android.widget.TextView;
  50import android.widget.Toast;
  51
  52import com.google.zxing.integration.android.IntentIntegrator;
  53import com.google.zxing.integration.android.IntentResult;
  54
  55import java.util.ArrayList;
  56import java.util.Arrays;
  57import java.util.Collections;
  58import java.util.List;
  59import java.util.concurrent.atomic.AtomicBoolean;
  60
  61import eu.siacs.conversations.Config;
  62import eu.siacs.conversations.R;
  63import eu.siacs.conversations.entities.Account;
  64import eu.siacs.conversations.entities.Blockable;
  65import eu.siacs.conversations.entities.Bookmark;
  66import eu.siacs.conversations.entities.Contact;
  67import eu.siacs.conversations.entities.Conversation;
  68import eu.siacs.conversations.entities.ListItem;
  69import eu.siacs.conversations.entities.Presence;
  70import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
  71import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
  72import eu.siacs.conversations.ui.adapter.ListItemAdapter;
  73import eu.siacs.conversations.utils.XmppUri;
  74import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
  75import eu.siacs.conversations.xmpp.XmppConnection;
  76import eu.siacs.conversations.xmpp.jid.InvalidJidException;
  77import eu.siacs.conversations.xmpp.jid.Jid;
  78
  79public class StartConversationActivity extends XmppActivity implements OnRosterUpdate, OnUpdateBlocklist {
  80
  81	public int conference_context_id;
  82	public int contact_context_id;
  83	private Tab mContactsTab;
  84	private Tab mConferencesTab;
  85	private ViewPager mViewPager;
  86	private MyListFragment mContactsListFragment = new MyListFragment();
  87	private List<ListItem> contacts = new ArrayList<>();
  88	private ArrayAdapter<ListItem> mContactsAdapter;
  89	private MyListFragment mConferenceListFragment = new MyListFragment();
  90	private List<ListItem> conferences = new ArrayList<ListItem>();
  91	private ArrayAdapter<ListItem> mConferenceAdapter;
  92	private List<String> mActivatedAccounts = new ArrayList<String>();
  93	private List<String> mKnownHosts;
  94	private List<String> mKnownConferenceHosts;
  95	private Invite mPendingInvite = null;
  96	private EditText mSearchEditText;
  97	private AtomicBoolean mRequestedContactsPermission = new AtomicBoolean(false);
  98	private final int REQUEST_SYNC_CONTACTS = 0x3b28cf;
  99	private final int REQUEST_CREATE_CONFERENCE = 0x3b39da;
 100
 101	private MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
 102
 103		@Override
 104		public boolean onMenuItemActionExpand(MenuItem item) {
 105			mSearchEditText.post(new Runnable() {
 106
 107				@Override
 108				public void run() {
 109					mSearchEditText.requestFocus();
 110					InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 111					imm.showSoftInput(mSearchEditText,
 112							InputMethodManager.SHOW_IMPLICIT);
 113				}
 114			});
 115
 116			return true;
 117		}
 118
 119		@Override
 120		public boolean onMenuItemActionCollapse(MenuItem item) {
 121			hideKeyboard();
 122			mSearchEditText.setText("");
 123			filter(null);
 124			return true;
 125		}
 126	};
 127	private boolean mHideOfflineContacts = false;
 128	private TabListener mTabListener = new TabListener() {
 129
 130		@Override
 131		public void onTabUnselected(Tab tab, FragmentTransaction ft) {
 132			return;
 133		}
 134
 135		@Override
 136		public void onTabSelected(Tab tab, FragmentTransaction ft) {
 137			mViewPager.setCurrentItem(tab.getPosition());
 138			onTabChanged();
 139		}
 140
 141		@Override
 142		public void onTabReselected(Tab tab, FragmentTransaction ft) {
 143			return;
 144		}
 145	};
 146	private ViewPager.SimpleOnPageChangeListener mOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
 147		@Override
 148		public void onPageSelected(int position) {
 149			if (getActionBar() != null) {
 150				getActionBar().setSelectedNavigationItem(position);
 151			}
 152			onTabChanged();
 153		}
 154	};
 155	private TextWatcher mSearchTextWatcher = new TextWatcher() {
 156
 157		@Override
 158		public void afterTextChanged(Editable editable) {
 159			filter(editable.toString());
 160		}
 161
 162		@Override
 163		public void beforeTextChanged(CharSequence s, int start, int count,
 164				int after) {
 165		}
 166
 167		@Override
 168		public void onTextChanged(CharSequence s, int start, int before,
 169				int count) {
 170		}
 171	};
 172
 173	private  TextView.OnEditorActionListener mSearchDone = new TextView.OnEditorActionListener() {
 174		@Override
 175		public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
 176			if (getActionBar().getSelectedNavigationIndex() == 0) {
 177				if (contacts.size() == 1) {
 178					openConversationForContact((Contact) contacts.get(0));
 179				} else {
 180					hideKeyboard();
 181					mContactsListFragment.getListView().requestFocus();
 182				}
 183			} else {
 184				if (conferences.size() == 1) {
 185					openConversationsForBookmark((Bookmark) conferences.get(0));
 186				} else {
 187					hideKeyboard();
 188					mConferenceListFragment.getListView().requestFocus();
 189				}
 190			}
 191			return true;
 192		}
 193	};
 194	private MenuItem mMenuSearchView;
 195	private ListItemAdapter.OnTagClickedListener mOnTagClickedListener = new ListItemAdapter.OnTagClickedListener() {
 196		@Override
 197		public void onTagClicked(String tag) {
 198			if (mMenuSearchView != null) {
 199				mMenuSearchView.expandActionView();
 200				mSearchEditText.setText("");
 201				mSearchEditText.append(tag);
 202				filter(tag);
 203			}
 204		}
 205	};
 206	private String mInitialJid;
 207	private Pair<Integer, Intent> mPostponedActivityResult;
 208	private UiCallback<Conversation> mAdhocConferenceCallback = new UiCallback<Conversation>() {
 209		@Override
 210		public void success(final Conversation conversation) {
 211			runOnUiThread(new Runnable() {
 212				@Override
 213				public void run() {
 214					hideToast();
 215					switchToConversation(conversation);
 216				}
 217			});
 218		}
 219
 220		@Override
 221		public void error(final int errorCode, Conversation object) {
 222			runOnUiThread(new Runnable() {
 223				@Override
 224				public void run() {
 225					replaceToast(getString(errorCode));
 226				}
 227			});
 228		}
 229
 230		@Override
 231		public void userInputRequried(PendingIntent pi, Conversation object) {
 232
 233		}
 234	};
 235	private Toast mToast;
 236
 237	protected void hideToast() {
 238		if (mToast != null) {
 239			mToast.cancel();
 240		}
 241	}
 242
 243	protected void replaceToast(String msg) {
 244		hideToast();
 245		mToast = Toast.makeText(this, msg ,Toast.LENGTH_LONG);
 246		mToast.show();
 247	}
 248
 249	@Override
 250	public void onRosterUpdate() {
 251		this.refreshUi();
 252	}
 253
 254	@Override
 255	public void onCreate(Bundle savedInstanceState) {
 256		super.onCreate(savedInstanceState);
 257		setContentView(R.layout.activity_start_conversation);
 258		mViewPager = (ViewPager) findViewById(R.id.start_conversation_view_pager);
 259		ActionBar actionBar = getActionBar();
 260		actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
 261
 262		mContactsTab = actionBar.newTab().setText(R.string.contacts)
 263			.setTabListener(mTabListener);
 264		mConferencesTab = actionBar.newTab().setText(R.string.conferences)
 265			.setTabListener(mTabListener);
 266		actionBar.addTab(mContactsTab);
 267		actionBar.addTab(mConferencesTab);
 268
 269		mViewPager.setOnPageChangeListener(mOnPageChangeListener);
 270		mViewPager.setAdapter(new FragmentPagerAdapter(getFragmentManager()) {
 271
 272			@Override
 273			public int getCount() {
 274				return 2;
 275			}
 276
 277			@Override
 278			public Fragment getItem(int position) {
 279				if (position == 0) {
 280					return mContactsListFragment;
 281				} else {
 282					return mConferenceListFragment;
 283				}
 284			}
 285		});
 286
 287		mConferenceAdapter = new ListItemAdapter(this, conferences);
 288		mConferenceListFragment.setListAdapter(mConferenceAdapter);
 289		mConferenceListFragment.setContextMenu(R.menu.conference_context);
 290		mConferenceListFragment
 291			.setOnListItemClickListener(new OnItemClickListener() {
 292
 293				@Override
 294				public void onItemClick(AdapterView<?> arg0, View arg1,
 295						int position, long arg3) {
 296					openConversationForBookmark(position);
 297				}
 298			});
 299
 300		mContactsAdapter = new ListItemAdapter(this, contacts);
 301		((ListItemAdapter) mContactsAdapter).setOnTagClickedListener(this.mOnTagClickedListener);
 302		mContactsListFragment.setListAdapter(mContactsAdapter);
 303		mContactsListFragment.setContextMenu(R.menu.contact_context);
 304		mContactsListFragment
 305			.setOnListItemClickListener(new OnItemClickListener() {
 306
 307				@Override
 308				public void onItemClick(AdapterView<?> arg0, View arg1,
 309						int position, long arg3) {
 310					openConversationForContact(position);
 311				}
 312			});
 313
 314		this.mHideOfflineContacts = getPreferences().getBoolean("hide_offline", false);
 315
 316	}
 317
 318	@Override
 319	public void onStart() {
 320		super.onStart();
 321		askForContactsPermissions();
 322	}
 323
 324	protected void openConversationForContact(int position) {
 325		Contact contact = (Contact) contacts.get(position);
 326		openConversationForContact(contact);
 327	}
 328
 329	protected void openConversationForContact(Contact contact) {
 330		Conversation conversation = xmppConnectionService
 331			.findOrCreateConversation(contact.getAccount(),
 332					contact.getJid(), false);
 333		switchToConversation(conversation);
 334	}
 335
 336	protected void openConversationForContact() {
 337		int position = contact_context_id;
 338		openConversationForContact(position);
 339	}
 340
 341	protected void openConversationForBookmark() {
 342		openConversationForBookmark(conference_context_id);
 343	}
 344
 345	protected void openConversationForBookmark(int position) {
 346		Bookmark bookmark = (Bookmark) conferences.get(position);
 347		openConversationsForBookmark(bookmark);
 348	}
 349
 350	protected void openConversationsForBookmark(Bookmark bookmark) {
 351		Jid jid = bookmark.getJid();
 352		if (jid == null) {
 353			Toast.makeText(this,R.string.invalid_jid,Toast.LENGTH_SHORT).show();
 354			return;
 355		}
 356		Conversation conversation = xmppConnectionService.findOrCreateConversation(bookmark.getAccount(),jid, true);
 357		conversation.setBookmark(bookmark);
 358		if (!conversation.getMucOptions().online()) {
 359			xmppConnectionService.joinMuc(conversation);
 360		}
 361		if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", true)) {
 362			bookmark.setAutojoin(true);
 363			xmppConnectionService.pushBookmarks(bookmark.getAccount());
 364		}
 365		switchToConversation(conversation);
 366	}
 367
 368	protected void openDetailsForContact() {
 369		int position = contact_context_id;
 370		Contact contact = (Contact) contacts.get(position);
 371		switchToContactDetails(contact);
 372	}
 373
 374	protected void toggleContactBlock() {
 375		final int position = contact_context_id;
 376		BlockContactDialog.show(this, xmppConnectionService, (Contact) contacts.get(position));
 377	}
 378
 379	protected void deleteContact() {
 380		final int position = contact_context_id;
 381		final Contact contact = (Contact) contacts.get(position);
 382		final AlertDialog.Builder builder = new AlertDialog.Builder(this);
 383		builder.setNegativeButton(R.string.cancel, null);
 384		builder.setTitle(R.string.action_delete_contact);
 385		builder.setMessage(getString(R.string.remove_contact_text,
 386				contact.getJid()));
 387		builder.setPositiveButton(R.string.delete, new OnClickListener() {
 388
 389			@Override
 390			public void onClick(DialogInterface dialog, int which) {
 391				xmppConnectionService.deleteContactOnServer(contact);
 392				filter(mSearchEditText.getText().toString());
 393			}
 394		});
 395		builder.create().show();
 396	}
 397
 398	protected void deleteConference() {
 399		int position = conference_context_id;
 400		final Bookmark bookmark = (Bookmark) conferences.get(position);
 401
 402		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 403		builder.setNegativeButton(R.string.cancel, null);
 404		builder.setTitle(R.string.delete_bookmark);
 405		builder.setMessage(getString(R.string.remove_bookmark_text,
 406				bookmark.getJid()));
 407		builder.setPositiveButton(R.string.delete, new OnClickListener() {
 408
 409			@Override
 410			public void onClick(DialogInterface dialog, int which) {
 411				bookmark.unregisterConversation();
 412				Account account = bookmark.getAccount();
 413				account.getBookmarks().remove(bookmark);
 414				xmppConnectionService.pushBookmarks(account);
 415				filter(mSearchEditText.getText().toString());
 416			}
 417		});
 418		builder.create().show();
 419
 420	}
 421
 422	@SuppressLint("InflateParams")
 423	protected void showCreateContactDialog(final String prefilledJid, final String fingerprint) {
 424		EnterJidDialog dialog = new EnterJidDialog(
 425			this, mKnownHosts, mActivatedAccounts,
 426			getString(R.string.create_contact), getString(R.string.create),
 427			prefilledJid, null, fingerprint == null
 428		);
 429
 430		dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() {
 431			@Override
 432			public boolean onEnterJidDialogPositive(Jid accountJid, Jid contactJid) throws EnterJidDialog.JidError {
 433				if (!xmppConnectionServiceBound) {
 434					return false;
 435				}
 436
 437				final Account account = xmppConnectionService.findAccountByJid(accountJid);
 438				if (account == null) {
 439					return true;
 440				}
 441
 442				final Contact contact = account.getRoster().getContact(contactJid);
 443				if (contact.showInRoster()) {
 444					throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists));
 445				} else {
 446					contact.addOtrFingerprint(fingerprint);
 447					xmppConnectionService.createContact(contact);
 448					switchToConversation(contact);
 449					return true;
 450				}
 451			}
 452		});
 453
 454		dialog.show();
 455	}
 456
 457	@SuppressLint("InflateParams")
 458	protected void showJoinConferenceDialog(final String prefilledJid) {
 459		final AlertDialog.Builder builder = new AlertDialog.Builder(this);
 460		builder.setTitle(R.string.join_conference);
 461		final View dialogView = getLayoutInflater().inflate(R.layout.join_conference_dialog, null);
 462		final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
 463		final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid);
 464		final TextView jabberIdDesc = (TextView) dialogView.findViewById(R.id.jabber_id);
 465		jabberIdDesc.setText(R.string.conference_address);
 466		jid.setHint(R.string.conference_address_example);
 467		jid.setAdapter(new KnownHostsAdapter(this, R.layout.simple_list_item, mKnownConferenceHosts));
 468		if (prefilledJid != null) {
 469			jid.append(prefilledJid);
 470		}
 471		populateAccountSpinner(this, mActivatedAccounts, spinner);
 472		final Checkable bookmarkCheckBox = (CheckBox) dialogView
 473			.findViewById(R.id.bookmark);
 474		builder.setView(dialogView);
 475		builder.setNegativeButton(R.string.cancel, null);
 476		builder.setPositiveButton(R.string.join, null);
 477		final AlertDialog dialog = builder.create();
 478		dialog.show();
 479		dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
 480				new View.OnClickListener() {
 481
 482					@Override
 483					public void onClick(final View v) {
 484						if (!xmppConnectionServiceBound) {
 485							return;
 486						}
 487						final Account account = getSelectedAccount(spinner);
 488						if (account == null) {
 489							return;
 490						}
 491						final Jid conferenceJid;
 492						try {
 493							conferenceJid = Jid.fromString(jid.getText().toString());
 494						} catch (final InvalidJidException e) {
 495							jid.setError(getString(R.string.invalid_jid));
 496							return;
 497						}
 498
 499						if (bookmarkCheckBox.isChecked()) {
 500							if (account.hasBookmarkFor(conferenceJid)) {
 501								jid.setError(getString(R.string.bookmark_already_exists));
 502							} else {
 503								final Bookmark bookmark = new Bookmark(account, conferenceJid.toBareJid());
 504								bookmark.setAutojoin(getPreferences().getBoolean("autojoin", true));
 505								String nick = conferenceJid.getResourcepart();
 506								if (nick != null && !nick.isEmpty()) {
 507									bookmark.setNick(nick);
 508								}
 509								account.getBookmarks().add(bookmark);
 510								xmppConnectionService.pushBookmarks(account);
 511								final Conversation conversation = xmppConnectionService
 512										.findOrCreateConversation(account,
 513												conferenceJid, true);
 514								conversation.setBookmark(bookmark);
 515								if (!conversation.getMucOptions().online()) {
 516									xmppConnectionService.joinMuc(conversation);
 517								}
 518								dialog.dismiss();
 519								switchToConversation(conversation);
 520							}
 521						} else {
 522							final Conversation conversation = xmppConnectionService
 523									.findOrCreateConversation(account,
 524											conferenceJid, true);
 525							if (!conversation.getMucOptions().online()) {
 526								xmppConnectionService.joinMuc(conversation);
 527							}
 528							dialog.dismiss();
 529							switchToConversation(conversation);
 530						}
 531					}
 532				});
 533	}
 534
 535	private void showCreateConferenceDialog() {
 536		final AlertDialog.Builder builder = new AlertDialog.Builder(this);
 537		builder.setTitle(R.string.create_conference);
 538		final View dialogView = getLayoutInflater().inflate(R.layout.create_conference_dialog, null);
 539		final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
 540		final EditText subject = (EditText) dialogView.findViewById(R.id.subject);
 541		populateAccountSpinner(this, mActivatedAccounts, spinner);
 542		builder.setView(dialogView);
 543		builder.setPositiveButton(R.string.choose_participants, new OnClickListener() {
 544			@Override
 545			public void onClick(DialogInterface dialog, int which) {
 546				if (!xmppConnectionServiceBound) {
 547					return;
 548				}
 549				final Account account = getSelectedAccount(spinner);
 550				if (account == null) {
 551					return;
 552				}
 553				Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class);
 554				intent.putExtra("multiple", true);
 555				intent.putExtra("show_enter_jid", true);
 556				intent.putExtra("subject", subject.getText().toString());
 557				intent.putExtra(EXTRA_ACCOUNT, account.getJid().toBareJid().toString());
 558				intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID,R.string.choose_participants);
 559				startActivityForResult(intent, REQUEST_CREATE_CONFERENCE);
 560			}
 561		});
 562		builder.setNegativeButton(R.string.cancel, null);
 563		builder.create().show();
 564	}
 565
 566	private Account getSelectedAccount(Spinner spinner) {
 567		if (!spinner.isEnabled()) {
 568			return null;
 569		}
 570		Jid jid;
 571		try {
 572			if (Config.DOMAIN_LOCK != null) {
 573				jid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
 574			} else {
 575				jid = Jid.fromString((String) spinner.getSelectedItem());
 576			}
 577		} catch (final InvalidJidException e) {
 578			return null;
 579		}
 580		return xmppConnectionService.findAccountByJid(jid);
 581	}
 582
 583	protected void switchToConversation(Contact contact) {
 584		Conversation conversation = xmppConnectionService
 585			.findOrCreateConversation(contact.getAccount(),
 586					contact.getJid(), false);
 587		switchToConversation(conversation);
 588	}
 589
 590	public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) {
 591		if (accounts.size() > 0) {
 592			ArrayAdapter<String> adapter = new ArrayAdapter<>(context, R.layout.simple_list_item, accounts);
 593			adapter.setDropDownViewResource(R.layout.simple_list_item);
 594			spinner.setAdapter(adapter);
 595			spinner.setEnabled(true);
 596		} else {
 597			ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
 598					R.layout.simple_list_item,
 599					Arrays.asList(new String[]{context.getString(R.string.no_accounts)}));
 600			adapter.setDropDownViewResource(R.layout.simple_list_item);
 601			spinner.setAdapter(adapter);
 602			spinner.setEnabled(false);
 603		}
 604	}
 605
 606	@Override
 607	public boolean onCreateOptionsMenu(Menu menu) {
 608		getMenuInflater().inflate(R.menu.start_conversation, menu);
 609		MenuItem menuCreateContact = menu.findItem(R.id.action_create_contact);
 610		MenuItem menuCreateConference = menu.findItem(R.id.action_conference);
 611		MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline);
 612		menuHideOffline.setChecked(this.mHideOfflineContacts);
 613		mMenuSearchView = menu.findItem(R.id.action_search);
 614		mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
 615		View mSearchView = mMenuSearchView.getActionView();
 616		mSearchEditText = (EditText) mSearchView
 617			.findViewById(R.id.search_field);
 618		mSearchEditText.addTextChangedListener(mSearchTextWatcher);
 619		mSearchEditText.setOnEditorActionListener(mSearchDone);
 620		if (getActionBar().getSelectedNavigationIndex() == 0) {
 621			menuCreateConference.setVisible(false);
 622		} else {
 623			menuCreateContact.setVisible(false);
 624		}
 625		if (mInitialJid != null) {
 626			mMenuSearchView.expandActionView();
 627			mSearchEditText.append(mInitialJid);
 628			filter(mInitialJid);
 629		}
 630		return super.onCreateOptionsMenu(menu);
 631	}
 632
 633	@Override
 634	public boolean onOptionsItemSelected(MenuItem item) {
 635		switch (item.getItemId()) {
 636			case R.id.action_create_contact:
 637				showCreateContactDialog(null,null);
 638				return true;
 639			case R.id.action_join_conference:
 640				showJoinConferenceDialog(null);
 641				return true;
 642			case R.id.action_create_conference:
 643				showCreateConferenceDialog();
 644				return true;
 645			case R.id.action_scan_qr_code:
 646				new IntentIntegrator(this).initiateScan();
 647				return true;
 648			case R.id.action_hide_offline:
 649				mHideOfflineContacts = !item.isChecked();
 650				getPreferences().edit().putBoolean("hide_offline", mHideOfflineContacts).commit();
 651				if (mSearchEditText != null) {
 652					filter(mSearchEditText.getText().toString());
 653				}
 654				invalidateOptionsMenu();
 655		}
 656		return super.onOptionsItemSelected(item);
 657	}
 658
 659	@Override
 660	public boolean onKeyUp(int keyCode, KeyEvent event) {
 661		if (keyCode == KeyEvent.KEYCODE_SEARCH && !event.isLongPress()) {
 662			openSearch();
 663			return true;
 664		}
 665		int c = event.getUnicodeChar();
 666		if (c > 32) {
 667			if (mSearchEditText != null && !mSearchEditText.isFocused()) {
 668				openSearch();
 669				mSearchEditText.append(Character.toString((char) c));
 670				return true;
 671			}
 672		}
 673		return super.onKeyUp(keyCode, event);
 674	}
 675
 676	private void openSearch() {
 677		if (mMenuSearchView != null) {
 678			mMenuSearchView.expandActionView();
 679		}
 680	}
 681
 682	@Override
 683	public void onActivityResult(int requestCode, int resultCode, Intent intent) {
 684		if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) {
 685			IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
 686			if (scanResult != null && scanResult.getFormatName() != null) {
 687				String data = scanResult.getContents();
 688				Invite invite = new Invite(data);
 689				if (xmppConnectionServiceBound) {
 690					invite.invite();
 691				} else if (invite.getJid() != null) {
 692					this.mPendingInvite = invite;
 693				} else {
 694					this.mPendingInvite = null;
 695				}
 696			}
 697		} else if (resultCode == RESULT_OK) {
 698			if (xmppConnectionServiceBound) {
 699				this.mPostponedActivityResult = null;
 700				if (requestCode == REQUEST_CREATE_CONFERENCE) {
 701					Account account = extractAccount(intent);
 702					final String subject = intent.getStringExtra("subject");
 703					List<Jid> jids = new ArrayList<>();
 704					if (intent.getBooleanExtra("multiple", false)) {
 705						String[] toAdd = intent.getStringArrayExtra("contacts");
 706						for (String item : toAdd) {
 707							try {
 708								jids.add(Jid.fromString(item));
 709							} catch (InvalidJidException e) {
 710								//ignored
 711							}
 712						}
 713					} else {
 714						try {
 715							jids.add(Jid.fromString(intent.getStringExtra("contact")));
 716						} catch (Exception e) {
 717							//ignored
 718						}
 719					}
 720					if (account != null && jids.size() > 0) {
 721						xmppConnectionService.createAdhocConference(account, subject, jids, mAdhocConferenceCallback);
 722						mToast = Toast.makeText(this, R.string.creating_conference,Toast.LENGTH_LONG);
 723						mToast.show();
 724					}
 725				}
 726			} else {
 727				this.mPostponedActivityResult = new Pair<>(requestCode, intent);
 728			}
 729		}
 730		super.onActivityResult(requestCode, requestCode, intent);
 731	}
 732
 733	private void askForContactsPermissions() {
 734		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 735			if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
 736				if (mRequestedContactsPermission.compareAndSet(false, true)) {
 737					if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
 738						AlertDialog.Builder builder = new AlertDialog.Builder(this);
 739						builder.setTitle(R.string.sync_with_contacts);
 740						builder.setMessage(R.string.sync_with_contacts_long);
 741						builder.setPositiveButton(R.string.next, new OnClickListener() {
 742							@Override
 743							public void onClick(DialogInterface dialog, int which) {
 744								if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 745									requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
 746								}
 747							}
 748						});
 749						if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
 750							builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
 751								@Override
 752								public void onDismiss(DialogInterface dialog) {
 753									if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 754										requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
 755									}
 756								}
 757							});
 758						}
 759						builder.create().show();
 760					} else {
 761						requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 0);
 762					}
 763				}
 764			}
 765		}
 766	}
 767
 768	@Override
 769	public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
 770		if (grantResults.length > 0)
 771			if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 772				if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
 773					xmppConnectionService.loadPhoneContacts();
 774				}
 775			}
 776	}
 777
 778	@Override
 779	protected void onBackendConnected() {
 780		if (mPostponedActivityResult != null) {
 781			onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
 782			this.mPostponedActivityResult = null;
 783		}
 784		this.mActivatedAccounts.clear();
 785		for (Account account : xmppConnectionService.getAccounts()) {
 786			if (account.getStatus() != Account.State.DISABLED) {
 787				if (Config.DOMAIN_LOCK != null) {
 788					this.mActivatedAccounts.add(account.getJid().getLocalpart());
 789				} else {
 790					this.mActivatedAccounts.add(account.getJid().toBareJid().toString());
 791				}
 792			}
 793		}
 794		final Intent intent = getIntent();
 795		final ActionBar ab = getActionBar();
 796		if (intent != null && intent.getBooleanExtra("init",false) && ab != null) {
 797			ab.setDisplayShowHomeEnabled(false);
 798			ab.setDisplayHomeAsUpEnabled(false);
 799			ab.setHomeButtonEnabled(false);
 800		}
 801		this.mKnownHosts = xmppConnectionService.getKnownHosts();
 802		this.mKnownConferenceHosts = xmppConnectionService.getKnownConferenceHosts();
 803		if (this.mPendingInvite != null) {
 804			mPendingInvite.invite();
 805			this.mPendingInvite = null;
 806		} else if (!handleIntent(getIntent())) {
 807			if (mSearchEditText != null) {
 808				filter(mSearchEditText.getText().toString());
 809			} else {
 810				filter(null);
 811			}
 812		}
 813		setIntent(null);
 814	}
 815
 816	@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
 817	Invite getInviteJellyBean(NdefRecord record) {
 818		return new Invite(record.toUri());
 819	}
 820
 821	protected boolean handleIntent(Intent intent) {
 822		if (intent == null || intent.getAction() == null) {
 823			return false;
 824		}
 825		switch (intent.getAction()) {
 826			case Intent.ACTION_SENDTO:
 827			case Intent.ACTION_VIEW:
 828				Uri uri = intent.getData();
 829				if (uri != null) {
 830					Log.d(Config.LOGTAG, "received uri=" + intent.getData());
 831					return new Invite(intent.getData()).invite();
 832				} else {
 833					return false;
 834				}
 835			case NfcAdapter.ACTION_NDEF_DISCOVERED:
 836				for (Parcelable message : getIntent().getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
 837					if (message instanceof NdefMessage) {
 838						Log.d(Config.LOGTAG, "received message=" + message);
 839						for (NdefRecord record : ((NdefMessage) message).getRecords()) {
 840							switch (record.getTnf()) {
 841								case NdefRecord.TNF_WELL_KNOWN:
 842									if (Arrays.equals(record.getType(), NdefRecord.RTD_URI)) {
 843										if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
 844											return getInviteJellyBean(record).invite();
 845										} else {
 846											byte[] payload = record.getPayload();
 847											if (payload[0] == 0) {
 848												return new Invite(Uri.parse(new String(Arrays.copyOfRange(
 849																	payload, 1, payload.length)))).invite();
 850											}
 851										}
 852									}
 853							}
 854						}
 855					}
 856				}
 857		}
 858		return false;
 859	}
 860
 861	private boolean handleJid(Invite invite) {
 862		List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid());
 863		if (invite.isMuc()) {
 864			Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
 865			if (muc != null) {
 866				switchToConversation(muc);
 867				return true;
 868			} else {
 869				showJoinConferenceDialog(invite.getJid().toBareJid().toString());
 870				return false;
 871			}
 872		} else if (contacts.size() == 0) {
 873			showCreateContactDialog(invite.getJid().toString(),invite.getFingerprint());
 874			return false;
 875		} else if (contacts.size() == 1) {
 876			Contact contact = contacts.get(0);
 877			if (invite.getFingerprint() != null) {
 878				if (contact.addOtrFingerprint(invite.getFingerprint())) {
 879					Log.d(Config.LOGTAG,"added new fingerprint");
 880					xmppConnectionService.syncRosterToDisk(contact.getAccount());
 881				}
 882			}
 883			switchToConversation(contact);
 884			return true;
 885		} else {
 886			if (mMenuSearchView != null) {
 887				mMenuSearchView.expandActionView();
 888				mSearchEditText.setText("");
 889				mSearchEditText.append(invite.getJid().toString());
 890				filter(invite.getJid().toString());
 891			} else {
 892				mInitialJid = invite.getJid().toString();
 893			}
 894			return true;
 895		}
 896	}
 897
 898	protected void filter(String needle) {
 899		if (xmppConnectionServiceBound) {
 900			this.filterContacts(needle);
 901			this.filterConferences(needle);
 902		}
 903	}
 904
 905	protected void filterContacts(String needle) {
 906		this.contacts.clear();
 907		for (Account account : xmppConnectionService.getAccounts()) {
 908			if (account.getStatus() != Account.State.DISABLED) {
 909				for (Contact contact : account.getRoster().getContacts()) {
 910					Presence.Status s = contact.getShownStatus();
 911					if (contact.showInRoster() && contact.match(this, needle)
 912							&& (!this.mHideOfflineContacts
 913							|| (needle != null && !needle.trim().isEmpty())
 914							|| s.compareTo(Presence.Status.OFFLINE) < 0)) {
 915						this.contacts.add(contact);
 916					}
 917				}
 918			}
 919		}
 920		Collections.sort(this.contacts);
 921		mContactsAdapter.notifyDataSetChanged();
 922	}
 923
 924	protected void filterConferences(String needle) {
 925		this.conferences.clear();
 926		for (Account account : xmppConnectionService.getAccounts()) {
 927			if (account.getStatus() != Account.State.DISABLED) {
 928				for (Bookmark bookmark : account.getBookmarks()) {
 929					if (bookmark.match(this, needle)) {
 930						this.conferences.add(bookmark);
 931					}
 932				}
 933			}
 934		}
 935		Collections.sort(this.conferences);
 936		mConferenceAdapter.notifyDataSetChanged();
 937	}
 938
 939	private void onTabChanged() {
 940		invalidateOptionsMenu();
 941	}
 942
 943	@Override
 944	public void OnUpdateBlocklist(final Status status) {
 945		refreshUi();
 946	}
 947
 948	@Override
 949	protected void refreshUiReal() {
 950		if (mSearchEditText != null) {
 951			filter(mSearchEditText.getText().toString());
 952		}
 953	}
 954
 955	public static class MyListFragment extends ListFragment {
 956		private AdapterView.OnItemClickListener mOnItemClickListener;
 957		private int mResContextMenu;
 958
 959		public void setContextMenu(final int res) {
 960			this.mResContextMenu = res;
 961		}
 962
 963		@Override
 964		public void onListItemClick(final ListView l, final View v, final int position, final long id) {
 965			if (mOnItemClickListener != null) {
 966				mOnItemClickListener.onItemClick(l, v, position, id);
 967			}
 968		}
 969
 970		public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
 971			this.mOnItemClickListener = l;
 972		}
 973
 974		@Override
 975		public void onViewCreated(final View view, final Bundle savedInstanceState) {
 976			super.onViewCreated(view, savedInstanceState);
 977			registerForContextMenu(getListView());
 978			getListView().setFastScrollEnabled(true);
 979		}
 980
 981		@Override
 982		public void onCreateContextMenu(final ContextMenu menu, final View v,
 983				final ContextMenuInfo menuInfo) {
 984			super.onCreateContextMenu(menu, v, menuInfo);
 985			final StartConversationActivity activity = (StartConversationActivity) getActivity();
 986			activity.getMenuInflater().inflate(mResContextMenu, menu);
 987			final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
 988			if (mResContextMenu == R.menu.conference_context) {
 989				activity.conference_context_id = acmi.position;
 990			} else if (mResContextMenu == R.menu.contact_context){
 991				activity.contact_context_id = acmi.position;
 992				final Blockable contact = (Contact) activity.contacts.get(acmi.position);
 993				final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
 994				XmppConnection xmpp = contact.getAccount().getXmppConnection();
 995				if (xmpp != null && xmpp.getFeatures().blocking()) {
 996					if (contact.isBlocked()) {
 997						blockUnblockItem.setTitle(R.string.unblock_contact);
 998					} else {
 999						blockUnblockItem.setTitle(R.string.block_contact);
1000					}
1001				} else {
1002					blockUnblockItem.setVisible(false);
1003				}
1004			}
1005		}
1006
1007		@Override
1008		public boolean onContextItemSelected(final MenuItem item) {
1009			StartConversationActivity activity = (StartConversationActivity) getActivity();
1010			switch (item.getItemId()) {
1011				case R.id.context_start_conversation:
1012					activity.openConversationForContact();
1013					break;
1014				case R.id.context_contact_details:
1015					activity.openDetailsForContact();
1016					break;
1017				case R.id.context_contact_block_unblock:
1018					activity.toggleContactBlock();
1019					break;
1020				case R.id.context_delete_contact:
1021					activity.deleteContact();
1022					break;
1023				case R.id.context_join_conference:
1024					activity.openConversationForBookmark();
1025					break;
1026				case R.id.context_delete_conference:
1027					activity.deleteConference();
1028			}
1029			return true;
1030		}
1031	}
1032
1033	private class Invite extends XmppUri {
1034
1035		public Invite(final Uri uri) {
1036			super(uri);
1037		}
1038
1039		public Invite(final String uri) {
1040			super(uri);
1041		}
1042
1043		boolean invite() {
1044			if (getJid() != null) {
1045				return handleJid(this);
1046			}
1047			return false;
1048		}
1049
1050		public boolean isMuc() {
1051			return muc;
1052		}
1053	}
1054}