StartConversationActivity.java

   1package eu.siacs.conversations.ui;
   2
   3import android.Manifest;
   4import android.annotation.SuppressLint;
   5import android.app.Dialog;
   6import android.app.PendingIntent;
   7import android.content.ActivityNotFoundException;
   8import android.content.Context;
   9import android.content.Intent;
  10import android.content.SharedPreferences;
  11import android.content.pm.PackageManager;
  12import android.net.Uri;
  13import android.os.Build;
  14import android.os.Bundle;
  15import android.text.Editable;
  16import android.text.Html;
  17import android.text.TextWatcher;
  18import android.text.method.LinkMovementMethod;
  19import android.util.Log;
  20import android.util.Pair;
  21import android.view.ContextMenu;
  22import android.view.ContextMenu.ContextMenuInfo;
  23import android.view.KeyEvent;
  24import android.view.Menu;
  25import android.view.MenuItem;
  26import android.view.View;
  27import android.view.ViewGroup;
  28import android.view.inputmethod.InputMethodManager;
  29import android.widget.AdapterView;
  30import android.widget.AdapterView.AdapterContextMenuInfo;
  31import android.widget.ArrayAdapter;
  32import android.widget.AutoCompleteTextView;
  33import android.widget.CheckBox;
  34import android.widget.EditText;
  35import android.widget.ListView;
  36import android.widget.Spinner;
  37import android.widget.TextView;
  38import android.widget.Toast;
  39
  40import androidx.annotation.NonNull;
  41import androidx.annotation.Nullable;
  42import androidx.appcompat.app.ActionBar;
  43import androidx.appcompat.app.AlertDialog;
  44import androidx.databinding.DataBindingUtil;
  45import androidx.fragment.app.Fragment;
  46import androidx.fragment.app.FragmentManager;
  47import androidx.fragment.app.FragmentTransaction;
  48import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
  49import androidx.viewpager.widget.PagerAdapter;
  50import androidx.viewpager.widget.ViewPager;
  51
  52import com.google.android.material.textfield.TextInputLayout;
  53
  54import java.util.ArrayList;
  55import java.util.Collections;
  56import java.util.List;
  57import java.util.concurrent.atomic.AtomicBoolean;
  58
  59import eu.siacs.conversations.Config;
  60import eu.siacs.conversations.R;
  61import eu.siacs.conversations.databinding.ActivityStartConversationBinding;
  62import eu.siacs.conversations.entities.Account;
  63import eu.siacs.conversations.entities.Bookmark;
  64import eu.siacs.conversations.entities.Contact;
  65import eu.siacs.conversations.entities.Conversation;
  66import eu.siacs.conversations.entities.ListItem;
  67import eu.siacs.conversations.entities.MucOptions;
  68import eu.siacs.conversations.entities.Presence;
  69import eu.siacs.conversations.services.QuickConversationsService;
  70import eu.siacs.conversations.services.XmppConnectionService;
  71import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
  72import eu.siacs.conversations.ui.adapter.ListItemAdapter;
  73import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
  74import eu.siacs.conversations.ui.util.JidDialog;
  75import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
  76import eu.siacs.conversations.ui.util.PendingItem;
  77import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
  78import eu.siacs.conversations.ui.widget.SwipeRefreshListFragment;
  79import eu.siacs.conversations.utils.AccountUtils;
  80import eu.siacs.conversations.utils.XmppUri;
  81import eu.siacs.conversations.xmpp.Jid;
  82import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
  83import eu.siacs.conversations.xmpp.XmppConnection;
  84
  85public class StartConversationActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, CreatePrivateGroupChatDialog.CreateConferenceDialogListener, JoinConferenceDialog.JoinConferenceDialogListener, SwipeRefreshLayout.OnRefreshListener, CreatePublicChannelDialog.CreatePublicChannelDialogListener {
  86
  87	public static final String EXTRA_INVITE_URI = "eu.siacs.conversations.invite_uri";
  88
  89	private final int REQUEST_SYNC_CONTACTS = 0x28cf;
  90	private final int REQUEST_CREATE_CONFERENCE = 0x39da;
  91	private final PendingItem<Intent> pendingViewIntent = new PendingItem<>();
  92	private final PendingItem<String> mInitialSearchValue = new PendingItem<>();
  93	private final AtomicBoolean oneShotKeyboardSuppress = new AtomicBoolean();
  94	public int conference_context_id;
  95	public int contact_context_id;
  96	private ListPagerAdapter mListPagerAdapter;
  97	private final List<ListItem> contacts = new ArrayList<>();
  98	private ListItemAdapter mContactsAdapter;
  99	private final List<ListItem> conferences = new ArrayList<>();
 100	private ListItemAdapter mConferenceAdapter;
 101	private final List<String> mActivatedAccounts = new ArrayList<>();
 102	private EditText mSearchEditText;
 103	private final AtomicBoolean mRequestedContactsPermission = new AtomicBoolean(false);
 104	private final AtomicBoolean mOpenedFab = new AtomicBoolean(false);
 105	private boolean mHideOfflineContacts = false;
 106	private boolean createdByViewIntent = false;
 107	private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
 108
 109		@Override
 110		public boolean onMenuItemActionExpand(MenuItem item) {
 111			mSearchEditText.post(() -> {
 112				updateSearchViewHint();
 113				mSearchEditText.requestFocus();
 114				if (oneShotKeyboardSuppress.compareAndSet(true, false)) {
 115					return;
 116				}
 117				InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 118				if (imm != null) {
 119					imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
 120				}
 121			});
 122			if (binding.speedDial.isOpen()) {
 123				binding.speedDial.close();
 124			}
 125			return true;
 126		}
 127
 128		@Override
 129		public boolean onMenuItemActionCollapse(MenuItem item) {
 130			SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this);
 131			mSearchEditText.setText("");
 132			filter(null);
 133			return true;
 134		}
 135	};
 136	private final TextWatcher mSearchTextWatcher = new TextWatcher() {
 137
 138		@Override
 139		public void afterTextChanged(Editable editable) {
 140			filter(editable.toString());
 141		}
 142
 143		@Override
 144		public void beforeTextChanged(CharSequence s, int start, int count, int after) {
 145		}
 146
 147		@Override
 148		public void onTextChanged(CharSequence s, int start, int before, int count) {
 149		}
 150	};
 151	private MenuItem mMenuSearchView;
 152	private final ListItemAdapter.OnTagClickedListener mOnTagClickedListener = new ListItemAdapter.OnTagClickedListener() {
 153		@Override
 154		public void onTagClicked(String tag) {
 155			if (mMenuSearchView != null) {
 156				mMenuSearchView.expandActionView();
 157				mSearchEditText.setText("");
 158				mSearchEditText.append(tag);
 159				filter(tag);
 160			}
 161		}
 162	};
 163	private Pair<Integer, Intent> mPostponedActivityResult;
 164	private Toast mToast;
 165	private final UiCallback<Conversation> mAdhocConferenceCallback = new UiCallback<Conversation>() {
 166		@Override
 167		public void success(final Conversation conversation) {
 168			runOnUiThread(() -> {
 169				hideToast();
 170				switchToConversation(conversation);
 171			});
 172		}
 173
 174		@Override
 175		public void error(final int errorCode, Conversation object) {
 176			runOnUiThread(() -> replaceToast(getString(errorCode)));
 177		}
 178
 179		@Override
 180		public void userInputRequired(PendingIntent pi, Conversation object) {
 181
 182		}
 183	};
 184	private ActivityStartConversationBinding binding;
 185	private final TextView.OnEditorActionListener mSearchDone = new TextView.OnEditorActionListener() {
 186		@Override
 187		public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
 188			int pos = binding.startConversationViewPager.getCurrentItem();
 189			if (pos == 0) {
 190				if (contacts.size() == 1) {
 191					openConversationForContact((Contact) contacts.get(0));
 192					return true;
 193				} else if (contacts.size() == 0 && conferences.size() == 1) {
 194					openConversationsForBookmark((Bookmark) conferences.get(0));
 195					return true;
 196				}
 197			} else {
 198				if (conferences.size() == 1) {
 199					openConversationsForBookmark((Bookmark) conferences.get(0));
 200					return true;
 201				} else if (conferences.size() == 0 && contacts.size() == 1) {
 202					openConversationForContact((Contact) contacts.get(0));
 203					return true;
 204				}
 205			}
 206			SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this);
 207			mListPagerAdapter.requestFocus(pos);
 208			return true;
 209		}
 210	};
 211
 212	public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) {
 213		if (accounts.size() > 0) {
 214			ArrayAdapter<String> adapter = new ArrayAdapter<>(context, R.layout.simple_list_item, accounts);
 215			adapter.setDropDownViewResource(R.layout.simple_list_item);
 216			spinner.setAdapter(adapter);
 217			spinner.setEnabled(true);
 218		} else {
 219			ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
 220					R.layout.simple_list_item,
 221					Collections.singletonList(context.getString(R.string.no_accounts)));
 222			adapter.setDropDownViewResource(R.layout.simple_list_item);
 223			spinner.setAdapter(adapter);
 224			spinner.setEnabled(false);
 225		}
 226	}
 227
 228	public static void launch(Context context) {
 229		final Intent intent = new Intent(context, StartConversationActivity.class);
 230		context.startActivity(intent);
 231	}
 232
 233	private static Intent createLauncherIntent(Context context) {
 234		final Intent intent = new Intent(context, StartConversationActivity.class);
 235		intent.setAction(Intent.ACTION_MAIN);
 236		intent.addCategory(Intent.CATEGORY_LAUNCHER);
 237		return intent;
 238	}
 239
 240	private static boolean isViewIntent(final Intent i) {
 241		return i != null && (Intent.ACTION_VIEW.equals(i.getAction()) || Intent.ACTION_SENDTO.equals(i.getAction()) || i.hasExtra(EXTRA_INVITE_URI));
 242	}
 243
 244	protected void hideToast() {
 245		if (mToast != null) {
 246			mToast.cancel();
 247		}
 248	}
 249
 250	protected void replaceToast(String msg) {
 251		hideToast();
 252		mToast = Toast.makeText(this, msg, Toast.LENGTH_LONG);
 253		mToast.show();
 254	}
 255
 256	@Override
 257	public void onRosterUpdate() {
 258		this.refreshUi();
 259	}
 260
 261	@Override
 262	public void onCreate(Bundle savedInstanceState) {
 263		super.onCreate(savedInstanceState);
 264		this.binding = DataBindingUtil.setContentView(this, R.layout.activity_start_conversation);
 265		setSupportActionBar(binding.toolbar);
 266		configureActionBar(getSupportActionBar());
 267
 268		binding.speedDial.inflate(R.menu.start_conversation_fab_submenu);
 269
 270		binding.tabLayout.setupWithViewPager(binding.startConversationViewPager);
 271		binding.startConversationViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
 272			@Override
 273			public void onPageSelected(int position) {
 274				updateSearchViewHint();
 275			}
 276		});
 277		mListPagerAdapter = new ListPagerAdapter(getSupportFragmentManager());
 278		binding.startConversationViewPager.setAdapter(mListPagerAdapter);
 279
 280		mConferenceAdapter = new ListItemAdapter(this, conferences);
 281		mContactsAdapter = new ListItemAdapter(this, contacts);
 282		mContactsAdapter.setOnTagClickedListener(this.mOnTagClickedListener);
 283
 284		final SharedPreferences preferences = getPreferences();
 285
 286		this.mHideOfflineContacts = QuickConversationsService.isConversations() && preferences.getBoolean("hide_offline", false);
 287
 288		final boolean startSearching = preferences.getBoolean("start_searching",getResources().getBoolean(R.bool.start_searching));
 289
 290		final Intent intent;
 291		if (savedInstanceState == null) {
 292			intent = getIntent();
 293		} else {
 294			createdByViewIntent = savedInstanceState.getBoolean("created_by_view_intent", false);
 295			final String search = savedInstanceState.getString("search");
 296			if (search != null) {
 297				mInitialSearchValue.push(search);
 298			}
 299			intent = savedInstanceState.getParcelable("intent");
 300		}
 301
 302		if (isViewIntent(intent)) {
 303			pendingViewIntent.push(intent);
 304			createdByViewIntent = true;
 305			setIntent(createLauncherIntent(this));
 306		} else if (startSearching && mInitialSearchValue.peek() == null) {
 307			mInitialSearchValue.push("");
 308		}
 309		mRequestedContactsPermission.set(savedInstanceState != null && savedInstanceState.getBoolean("requested_contacts_permission",false));
 310		mOpenedFab.set(savedInstanceState != null && savedInstanceState.getBoolean("opened_fab",false));
 311		binding.speedDial.setOnActionSelectedListener(actionItem -> {
 312			final String searchString = mSearchEditText != null ? mSearchEditText.getText().toString() : null;
 313			final String prefilled;
 314			if (isValidJid(searchString)) {
 315				prefilled = Jid.ofEscaped(searchString).toEscapedString();
 316			} else {
 317				prefilled = null;
 318			}
 319			switch (actionItem.getId()) {
 320				case R.id.discover_public_channels:
 321					startActivity(new Intent(this, ChannelDiscoveryActivity.class));
 322					break;
 323				case R.id.join_public_channel:
 324					showJoinConferenceDialog(prefilled);
 325					break;
 326				case R.id.create_private_group_chat:
 327					showCreatePrivateGroupChatDialog();
 328					break;
 329				case R.id.create_public_channel:
 330					showPublicChannelDialog();
 331					break;
 332				case R.id.create_contact:
 333					showCreateContactDialog(prefilled,null);
 334					break;
 335			}
 336			return false;
 337		});
 338	}
 339
 340	public static boolean isValidJid(String input) {
 341		try {
 342			Jid jid = Jid.ofEscaped(input);
 343			return !jid.isDomainJid();
 344		} catch (IllegalArgumentException e) {
 345			return false;
 346		}
 347	}
 348
 349	@Override
 350	public void onSaveInstanceState(Bundle savedInstanceState) {
 351		Intent pendingIntent = pendingViewIntent.peek();
 352		savedInstanceState.putParcelable("intent", pendingIntent != null ? pendingIntent : getIntent());
 353		savedInstanceState.putBoolean("requested_contacts_permission",mRequestedContactsPermission.get());
 354		savedInstanceState.putBoolean("opened_fab",mOpenedFab.get());
 355		savedInstanceState.putBoolean("created_by_view_intent",createdByViewIntent);
 356		if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) {
 357			savedInstanceState.putString("search", mSearchEditText != null ? mSearchEditText.getText().toString() : null);
 358		}
 359		super.onSaveInstanceState(savedInstanceState);
 360	}
 361
 362	@Override
 363	public void onStart() {
 364		super.onStart();
 365		final int theme = findTheme();
 366		if (this.mTheme != theme) {
 367			recreate();
 368		} else {
 369			if (pendingViewIntent.peek() == null) {
 370				askForContactsPermissions();
 371			}
 372		}
 373		mConferenceAdapter.refreshSettings();
 374		mContactsAdapter.refreshSettings();
 375	}
 376
 377	@Override
 378	public void onNewIntent(final Intent intent) {
 379		super.onNewIntent(intent);
 380		if (xmppConnectionServiceBound) {
 381			processViewIntent(intent);
 382		} else {
 383			pendingViewIntent.push(intent);
 384		}
 385		setIntent(createLauncherIntent(this));
 386	}
 387
 388	protected void openConversationForContact(int position) {
 389		Contact contact = (Contact) contacts.get(position);
 390		openConversationForContact(contact);
 391	}
 392
 393	protected void openConversationForContact(Contact contact) {
 394		Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
 395		SoftKeyboardUtils.hideSoftKeyboard(this);
 396		switchToConversation(conversation);
 397	}
 398
 399	protected void openConversationForBookmark(int position) {
 400		Bookmark bookmark = (Bookmark) conferences.get(position);
 401		openConversationsForBookmark(bookmark);
 402	}
 403
 404	protected void shareBookmarkUri() {
 405		shareBookmarkUri(conference_context_id);
 406	}
 407
 408	protected void shareBookmarkUri(int position) {
 409		Bookmark bookmark = (Bookmark) conferences.get(position);
 410		shareAsChannel(this, bookmark.getJid().asBareJid().toEscapedString());
 411	}
 412
 413	public static void shareAsChannel(final Context context, final String address) {
 414		Intent shareIntent = new Intent();
 415		shareIntent.setAction(Intent.ACTION_SEND);
 416		shareIntent.putExtra(Intent.EXTRA_TEXT, "xmpp:" + address + "?join");
 417		shareIntent.setType("text/plain");
 418		try {
 419			context.startActivity(Intent.createChooser(shareIntent, context.getText(R.string.share_uri_with)));
 420		} catch (ActivityNotFoundException e) {
 421			Toast.makeText(context, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
 422		}
 423	}
 424
 425	protected void openConversationsForBookmark(Bookmark bookmark) {
 426		final Jid jid = bookmark.getFullJid();
 427		if (jid == null) {
 428			Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
 429			return;
 430		}
 431		Conversation conversation = xmppConnectionService.findOrCreateConversation(bookmark.getAccount(), jid, true, true, true);
 432		bookmark.setConversation(conversation);
 433		if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", getResources().getBoolean(R.bool.autojoin))) {
 434			bookmark.setAutojoin(true);
 435			xmppConnectionService.createBookmark(bookmark.getAccount(), bookmark);
 436		}
 437		SoftKeyboardUtils.hideSoftKeyboard(this);
 438		switchToConversation(conversation);
 439	}
 440
 441	protected void openDetailsForContact() {
 442		int position = contact_context_id;
 443		Contact contact = (Contact) contacts.get(position);
 444		switchToContactDetails(contact);
 445	}
 446
 447	protected void showQrForContact() {
 448		int position = contact_context_id;
 449		Contact contact = (Contact) contacts.get(position);
 450		showQrCode("xmpp:"+contact.getJid().asBareJid().toEscapedString());
 451	}
 452
 453	protected void toggleContactBlock() {
 454		final int position = contact_context_id;
 455		BlockContactDialog.show(this, (Contact) contacts.get(position));
 456	}
 457
 458	protected void deleteContact() {
 459		final int position = contact_context_id;
 460		final Contact contact = (Contact) contacts.get(position);
 461		final AlertDialog.Builder builder = new AlertDialog.Builder(this);
 462		builder.setNegativeButton(R.string.cancel, null);
 463		builder.setTitle(R.string.action_delete_contact);
 464		builder.setMessage(JidDialog.style(this, R.string.remove_contact_text, contact.getJid().toEscapedString()));
 465		builder.setPositiveButton(R.string.delete, (dialog, which) -> {
 466			xmppConnectionService.deleteContactOnServer(contact);
 467			filter(mSearchEditText.getText().toString());
 468		});
 469		builder.create().show();
 470	}
 471
 472	protected void deleteConference() {
 473		int position = conference_context_id;
 474		final Bookmark bookmark = (Bookmark) conferences.get(position);
 475
 476		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 477		builder.setNegativeButton(R.string.cancel, null);
 478		builder.setTitle(R.string.delete_bookmark);
 479		builder.setMessage(JidDialog.style(this, R.string.remove_bookmark_text, bookmark.getJid().toEscapedString()));
 480		builder.setPositiveButton(R.string.delete, (dialog, which) -> {
 481			bookmark.setConversation(null);
 482			final Account account = bookmark.getAccount();
 483			xmppConnectionService.deleteBookmark(account, bookmark);
 484			filter(mSearchEditText.getText().toString());
 485		});
 486		builder.create().show();
 487
 488	}
 489
 490	@SuppressLint("InflateParams")
 491	protected void showCreateContactDialog(final String prefilledJid, final Invite invite) {
 492		FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 493		Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 494		if (prev != null) {
 495			ft.remove(prev);
 496		}
 497		ft.addToBackStack(null);
 498		EnterJidDialog dialog = EnterJidDialog.newInstance(
 499				mActivatedAccounts,
 500				getString(R.string.add_contact),
 501				getString(R.string.add),
 502				prefilledJid,
 503				invite == null ? null : invite.account,
 504				invite == null || !invite.hasFingerprints(),
 505				true
 506		);
 507
 508		dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> {
 509			if (!xmppConnectionServiceBound) {
 510				return false;
 511			}
 512
 513			final Account account = xmppConnectionService.findAccountByJid(accountJid);
 514			if (account == null) {
 515				return true;
 516			}
 517
 518			final Contact contact = account.getRoster().getContact(contactJid);
 519			if (invite != null && invite.getName() != null) {
 520				contact.setServerName(invite.getName());
 521			}
 522			if (contact.isSelf()) {
 523				switchToConversation(contact);
 524				return true;
 525			} else if (contact.showInRoster()) {
 526				throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists));
 527			} else {
 528				xmppConnectionService.createContact(contact, true);
 529				if (invite != null && invite.hasFingerprints()) {
 530					xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
 531				}
 532				switchToConversationDoNotAppend(contact, invite == null ? null : invite.getBody());
 533				return true;
 534			}
 535		});
 536		dialog.show(ft, FRAGMENT_TAG_DIALOG);
 537	}
 538
 539	@SuppressLint("InflateParams")
 540	protected void showJoinConferenceDialog(final String prefilledJid) {
 541		FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 542		Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 543		if (prev != null) {
 544			ft.remove(prev);
 545		}
 546		ft.addToBackStack(null);
 547		JoinConferenceDialog joinConferenceFragment = JoinConferenceDialog.newInstance(prefilledJid, mActivatedAccounts);
 548		joinConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
 549	}
 550
 551	private void showCreatePrivateGroupChatDialog() {
 552		FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 553		Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 554		if (prev != null) {
 555			ft.remove(prev);
 556		}
 557		ft.addToBackStack(null);
 558		CreatePrivateGroupChatDialog createConferenceFragment = CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts);
 559		createConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
 560	}
 561
 562	private void showPublicChannelDialog() {
 563		FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 564		Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 565		if (prev != null) {
 566			ft.remove(prev);
 567		}
 568		ft.addToBackStack(null);
 569		CreatePublicChannelDialog dialog = CreatePublicChannelDialog.newInstance(mActivatedAccounts);
 570		dialog.show(ft, FRAGMENT_TAG_DIALOG);
 571	}
 572
 573	public static Account getSelectedAccount(Context context, Spinner spinner) {
 574		if (spinner == null || !spinner.isEnabled()) {
 575			return null;
 576		}
 577		if (context instanceof XmppActivity) {
 578			Jid jid;
 579			try {
 580				if (Config.DOMAIN_LOCK != null) {
 581					jid = Jid.ofEscaped((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
 582				} else {
 583					jid = Jid.ofEscaped((String) spinner.getSelectedItem());
 584				}
 585			} catch (final IllegalArgumentException e) {
 586				return null;
 587			}
 588			final XmppConnectionService service = ((XmppActivity) context).xmppConnectionService;
 589			if (service == null) {
 590				return null;
 591			}
 592			return service.findAccountByJid(jid);
 593		} else {
 594			return null;
 595		}
 596	}
 597
 598	protected void switchToConversation(Contact contact) {
 599		Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
 600		switchToConversation(conversation);
 601	}
 602
 603	protected void switchToConversationDoNotAppend(Contact contact, String body) {
 604		Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
 605		switchToConversationDoNotAppend(conversation, body);
 606	}
 607
 608	@Override
 609	public void invalidateOptionsMenu() {
 610		boolean isExpanded = mMenuSearchView != null && mMenuSearchView.isActionViewExpanded();
 611		String text = mSearchEditText != null ? mSearchEditText.getText().toString() : "";
 612		if (isExpanded) {
 613			mInitialSearchValue.push(text);
 614			oneShotKeyboardSuppress.set(true);
 615		}
 616		super.invalidateOptionsMenu();
 617	}
 618
 619	private void updateSearchViewHint() {
 620		if (binding == null || mSearchEditText == null) {
 621			return;
 622		}
 623		if (binding.startConversationViewPager.getCurrentItem() == 0) {
 624			mSearchEditText.setHint(R.string.search_contacts);
 625			mSearchEditText.setContentDescription(getString(R.string.search_contacts));
 626		} else {
 627			mSearchEditText.setHint(R.string.search_bookmarks);
 628			mSearchEditText.setContentDescription(getString(R.string.search_bookmarks));
 629		}
 630	}
 631
 632	@Override
 633	public boolean onCreateOptionsMenu(Menu menu) {
 634		getMenuInflater().inflate(R.menu.start_conversation, menu);
 635		AccountUtils.showHideMenuItems(menu);
 636		MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline);
 637		MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code);
 638		qrCodeScanMenuItem.setVisible(isCameraFeatureAvailable());
 639		if (QuickConversationsService.isQuicksy()) {
 640			menuHideOffline.setVisible(false);
 641		} else {
 642			menuHideOffline.setVisible(true);
 643			menuHideOffline.setChecked(this.mHideOfflineContacts);
 644		}
 645		mMenuSearchView = menu.findItem(R.id.action_search);
 646		mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
 647		View mSearchView = mMenuSearchView.getActionView();
 648		mSearchEditText = mSearchView.findViewById(R.id.search_field);
 649		mSearchEditText.addTextChangedListener(mSearchTextWatcher);
 650		mSearchEditText.setOnEditorActionListener(mSearchDone);
 651		String initialSearchValue = mInitialSearchValue.pop();
 652		if (initialSearchValue != null) {
 653			mMenuSearchView.expandActionView();
 654			mSearchEditText.append(initialSearchValue);
 655			filter(initialSearchValue);
 656		}
 657		updateSearchViewHint();
 658		return super.onCreateOptionsMenu(menu);
 659	}
 660
 661	@Override
 662	public boolean onOptionsItemSelected(MenuItem item) {
 663		if (MenuDoubleTabUtil.shouldIgnoreTap()) {
 664			return false;
 665		}
 666		switch (item.getItemId()) {
 667			case android.R.id.home:
 668				navigateBack();
 669				return true;
 670			case R.id.action_scan_qr_code:
 671				UriHandlerActivity.scan(this);
 672				return true;
 673			case R.id.action_hide_offline:
 674				mHideOfflineContacts = !item.isChecked();
 675				getPreferences().edit().putBoolean("hide_offline", mHideOfflineContacts).apply();
 676				if (mSearchEditText != null) {
 677					filter(mSearchEditText.getText().toString());
 678				}
 679				invalidateOptionsMenu();
 680		}
 681		return super.onOptionsItemSelected(item);
 682	}
 683
 684	@Override
 685	public boolean onKeyUp(int keyCode, KeyEvent event) {
 686		if (keyCode == KeyEvent.KEYCODE_SEARCH && !event.isLongPress()) {
 687			openSearch();
 688			return true;
 689		}
 690		int c = event.getUnicodeChar();
 691		if (c > 32) {
 692			if (mSearchEditText != null && !mSearchEditText.isFocused()) {
 693				openSearch();
 694				mSearchEditText.append(Character.toString((char) c));
 695				return true;
 696			}
 697		}
 698		return super.onKeyUp(keyCode, event);
 699	}
 700
 701	private void openSearch() {
 702		if (mMenuSearchView != null) {
 703			mMenuSearchView.expandActionView();
 704		}
 705	}
 706
 707	@Override
 708	public void onActivityResult(int requestCode, int resultCode, Intent intent) {
 709		if (resultCode == RESULT_OK) {
 710			if (xmppConnectionServiceBound) {
 711				this.mPostponedActivityResult = null;
 712				if (requestCode == REQUEST_CREATE_CONFERENCE) {
 713					Account account = extractAccount(intent);
 714					final String name = intent.getStringExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME);
 715					final List<Jid> jids = ChooseContactActivity.extractJabberIds(intent);
 716					if (account != null && jids.size() > 0) {
 717						if (xmppConnectionService.createAdhocConference(account, name, jids, mAdhocConferenceCallback)) {
 718							mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
 719							mToast.show();
 720						}
 721					}
 722				}
 723			} else {
 724				this.mPostponedActivityResult = new Pair<>(requestCode, intent);
 725			}
 726		}
 727		super.onActivityResult(requestCode, requestCode, intent);
 728	}
 729
 730	private void askForContactsPermissions() {
 731		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 732			if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
 733				if (mRequestedContactsPermission.compareAndSet(false, true)) {
 734					if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
 735						AlertDialog.Builder builder = new AlertDialog.Builder(this);
 736						builder.setTitle(R.string.sync_with_contacts);
 737						if (QuickConversationsService.isQuicksy()) {
 738							builder.setMessage(Html.fromHtml(getString(R.string.sync_with_contacts_quicksy)));
 739                        } else {
 740                            builder.setMessage(getString(R.string.sync_with_contacts_long, getString(R.string.app_name)));
 741                        }
 742						builder.setPositiveButton(R.string.next, (dialog, which) -> requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS));
 743						builder.setOnDismissListener(dialog -> requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS));
 744						builder.setCancelable(false);
 745						AlertDialog dialog = builder.create();
 746						dialog.setCanceledOnTouchOutside(false);
 747						dialog.setOnShowListener(dialogInterface -> {
 748							final TextView tv =  dialog.findViewById(android.R.id.message);
 749							if (tv != null) {
 750								tv.setMovementMethod(LinkMovementMethod.getInstance());
 751							}
 752						});
 753						dialog.show();
 754					} else {
 755						requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
 756					}
 757				}
 758			}
 759		}
 760	}
 761
 762	@Override
 763	public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
 764		if (grantResults.length > 0)
 765			if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 766				ScanActivity.onRequestPermissionResult(this, requestCode, grantResults);
 767				if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
 768					if (QuickConversationsService.isQuicksy()) {
 769						setRefreshing(true);
 770					}
 771					xmppConnectionService.loadPhoneContacts();
 772					xmppConnectionService.startContactObserver();
 773				}
 774			}
 775	}
 776
 777	private void configureHomeButton() {
 778		final ActionBar actionBar = getSupportActionBar();
 779		if (actionBar == null) {
 780			return;
 781		}
 782		boolean openConversations = !createdByViewIntent && !xmppConnectionService.isConversationsListEmpty(null);
 783		actionBar.setDisplayHomeAsUpEnabled(openConversations);
 784		actionBar.setDisplayHomeAsUpEnabled(openConversations);
 785
 786	}
 787
 788	@Override
 789	protected void onBackendConnected() {
 790
 791		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
 792			xmppConnectionService.getQuickConversationsService().considerSyncBackground(false);
 793		}
 794		if (mPostponedActivityResult != null) {
 795			onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
 796			this.mPostponedActivityResult = null;
 797		}
 798		this.mActivatedAccounts.clear();
 799		this.mActivatedAccounts.addAll(AccountUtils.getEnabledAccounts(xmppConnectionService));
 800		configureHomeButton();
 801		Intent intent = pendingViewIntent.pop();
 802		if (intent != null && processViewIntent(intent)) {
 803			filter(null);
 804		} else {
 805			if (mSearchEditText != null) {
 806				filter(mSearchEditText.getText().toString());
 807			} else {
 808				filter(null);
 809			}
 810		}
 811		Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 812		if (fragment instanceof OnBackendConnected) {
 813			Log.d(Config.LOGTAG, "calling on backend connected on dialog");
 814			((OnBackendConnected) fragment).onBackendConnected();
 815		}
 816		if (QuickConversationsService.isQuicksy()) {
 817			setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing());
 818		}
 819		if (QuickConversationsService.isConversations() && AccountUtils.hasEnabledAccounts(xmppConnectionService) && this.contacts.size() == 0 && this.conferences.size() == 0 && mOpenedFab.compareAndSet(false,true)) {
 820			binding.speedDial.open();
 821		}
 822	}
 823
 824	protected boolean processViewIntent(@NonNull Intent intent) {
 825		final String inviteUri = intent.getStringExtra(EXTRA_INVITE_URI);
 826		if (inviteUri != null) {
 827			final Invite invite = new Invite(inviteUri);
 828			invite.account = intent.getStringExtra(EXTRA_ACCOUNT);
 829			if (invite.isValidJid()) {
 830				return invite.invite();
 831			}
 832		}
 833		final String action = intent.getAction();
 834		if (action == null) {
 835			return false;
 836		}
 837		switch (action) {
 838			case Intent.ACTION_SENDTO:
 839			case Intent.ACTION_VIEW:
 840				Uri uri = intent.getData();
 841				if (uri != null) {
 842					Invite invite = new Invite(intent.getData(), intent.getBooleanExtra("scanned", false));
 843					invite.account = intent.getStringExtra(EXTRA_ACCOUNT);
 844					invite.forceDialog = intent.getBooleanExtra("force_dialog", false);
 845					return invite.invite();
 846				} else {
 847					return false;
 848				}
 849		}
 850		return false;
 851	}
 852
 853	private boolean handleJid(Invite invite) {
 854		List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid(), invite.account);
 855		if (invite.isAction(XmppUri.ACTION_JOIN)) {
 856			Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
 857			if (muc != null && !invite.forceDialog) {
 858				switchToConversationDoNotAppend(muc, invite.getBody());
 859				return true;
 860			} else {
 861				showJoinConferenceDialog(invite.getJid().asBareJid().toEscapedString());
 862				return false;
 863			}
 864		} else if (contacts.size() == 0) {
 865			showCreateContactDialog(invite.getJid().toEscapedString(), invite);
 866			return false;
 867		} else if (contacts.size() == 1) {
 868			Contact contact = contacts.get(0);
 869			if (!invite.isSafeSource() && invite.hasFingerprints()) {
 870				displayVerificationWarningDialog(contact, invite);
 871			} else {
 872				if (invite.hasFingerprints()) {
 873					if (xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints())) {
 874						Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
 875					}
 876				}
 877				if (invite.account != null) {
 878					xmppConnectionService.getShortcutService().report(contact);
 879				}
 880				switchToConversationDoNotAppend(contact, invite.getBody());
 881			}
 882			return true;
 883		} else {
 884			if (mMenuSearchView != null) {
 885				mMenuSearchView.expandActionView();
 886				mSearchEditText.setText("");
 887				mSearchEditText.append(invite.getJid().toEscapedString());
 888				filter(invite.getJid().toEscapedString());
 889			} else {
 890				mInitialSearchValue.push(invite.getJid().toEscapedString());
 891			}
 892			return true;
 893		}
 894	}
 895
 896	private void displayVerificationWarningDialog(final Contact contact, final Invite invite) {
 897		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 898		builder.setTitle(R.string.verify_omemo_keys);
 899		View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
 900		final CheckBox isTrustedSource = view.findViewById(R.id.trusted_source);
 901		TextView warning = view.findViewById(R.id.warning);
 902		warning.setText(JidDialog.style(this, R.string.verifying_omemo_keys_trusted_source, contact.getJid().asBareJid().toEscapedString(), contact.getDisplayName()));
 903		builder.setView(view);
 904		builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
 905			if (isTrustedSource.isChecked() && invite.hasFingerprints()) {
 906				xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
 907			}
 908			switchToConversationDoNotAppend(contact, invite.getBody());
 909		});
 910		builder.setNegativeButton(R.string.cancel, (dialog, which) -> StartConversationActivity.this.finish());
 911		AlertDialog dialog = builder.create();
 912		dialog.setCanceledOnTouchOutside(false);
 913		dialog.setOnCancelListener(dialog1 -> StartConversationActivity.this.finish());
 914		dialog.show();
 915	}
 916
 917	protected void filter(String needle) {
 918		if (xmppConnectionServiceBound) {
 919			this.filterContacts(needle);
 920			this.filterConferences(needle);
 921		}
 922	}
 923
 924	protected void filterContacts(String needle) {
 925		this.contacts.clear();
 926		final List<Account> accounts = xmppConnectionService.getAccounts();
 927		for (Account account : accounts) {
 928			if (account.getStatus() != Account.State.DISABLED) {
 929				for (Contact contact : account.getRoster().getContacts()) {
 930					Presence.Status s = contact.getShownStatus();
 931					if (contact.showInContactList() && contact.match(this, needle)
 932							&& (!this.mHideOfflineContacts
 933							|| (needle != null && !needle.trim().isEmpty())
 934							|| s.compareTo(Presence.Status.OFFLINE) < 0)) {
 935						this.contacts.add(contact);
 936					}
 937				}
 938			}
 939		}
 940		Collections.sort(this.contacts);
 941		mContactsAdapter.notifyDataSetChanged();
 942	}
 943
 944	protected void filterConferences(String needle) {
 945		this.conferences.clear();
 946		for (Account account : xmppConnectionService.getAccounts()) {
 947			if (account.getStatus() != Account.State.DISABLED) {
 948				for (Bookmark bookmark : account.getBookmarks()) {
 949					if (bookmark.match(this, needle)) {
 950						this.conferences.add(bookmark);
 951					}
 952				}
 953			}
 954		}
 955		Collections.sort(this.conferences);
 956		mConferenceAdapter.notifyDataSetChanged();
 957	}
 958
 959	@Override
 960	public void OnUpdateBlocklist(final Status status) {
 961		refreshUi();
 962	}
 963
 964	@Override
 965	protected void refreshUiReal() {
 966		if (mSearchEditText != null) {
 967			filter(mSearchEditText.getText().toString());
 968		}
 969		configureHomeButton();
 970		if (QuickConversationsService.isQuicksy()) {
 971			setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing());
 972		}
 973	}
 974
 975	@Override
 976	public void onBackPressed() {
 977		if (binding.speedDial.isOpen()) {
 978			binding.speedDial.close();
 979			return;
 980		}
 981		navigateBack();
 982	}
 983
 984	private void navigateBack() {
 985		if (!createdByViewIntent && xmppConnectionService != null && !xmppConnectionService.isConversationsListEmpty(null)) {
 986			Intent intent = new Intent(this, ConversationsActivity.class);
 987			intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
 988			startActivity(intent);
 989		}
 990		finish();
 991	}
 992
 993	@Override
 994	public void onCreateDialogPositiveClick(Spinner spinner, String name) {
 995		if (!xmppConnectionServiceBound) {
 996			return;
 997		}
 998		final Account account = getSelectedAccount(this, spinner);
 999		if (account == null) {
1000			return;
1001		}
1002		Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class);
1003		intent.putExtra(ChooseContactActivity.EXTRA_SHOW_ENTER_JID, false);
1004		intent.putExtra(ChooseContactActivity.EXTRA_SELECT_MULTIPLE, true);
1005		intent.putExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME, name.trim());
1006		intent.putExtra(ChooseContactActivity.EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
1007		intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants);
1008		startActivityForResult(intent, REQUEST_CREATE_CONFERENCE);
1009	}
1010
1011	@Override
1012	public void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, TextInputLayout layout, AutoCompleteTextView jid, boolean isBookmarkChecked) {
1013		if (!xmppConnectionServiceBound) {
1014			return;
1015		}
1016		final Account account = getSelectedAccount(this, spinner);
1017		if (account == null) {
1018			return;
1019		}
1020		final String input = jid.getText().toString();
1021		Jid conferenceJid;
1022		try {
1023			conferenceJid = Jid.ofEscaped(input);
1024		} catch (final IllegalArgumentException e) {
1025			final XmppUri xmppUri = new XmppUri(input);
1026			if (xmppUri.isValidJid() && xmppUri.isAction(XmppUri.ACTION_JOIN)) {
1027				final Editable editable = jid.getEditableText();
1028				editable.clear();
1029				editable.append(xmppUri.getJid().toEscapedString());
1030				conferenceJid = xmppUri.getJid();
1031			} else {
1032				layout.setError(getString(R.string.invalid_jid));
1033				return;
1034			}
1035		}
1036
1037		if (isBookmarkChecked) {
1038			Bookmark bookmark = account.getBookmark(conferenceJid);
1039			if (bookmark != null) {
1040				dialog.dismiss();
1041				openConversationsForBookmark(bookmark);
1042			} else {
1043				bookmark = new Bookmark(account, conferenceJid.asBareJid());
1044				bookmark.setAutojoin(getBooleanPreference("autojoin", R.bool.autojoin));
1045				final String nick = conferenceJid.getResource();
1046				if (nick != null && !nick.isEmpty() && !nick.equals(MucOptions.defaultNick(account))) {
1047					bookmark.setNick(nick);
1048				}
1049				xmppConnectionService.createBookmark(account, bookmark);
1050				final Conversation conversation = xmppConnectionService
1051						.findOrCreateConversation(account, conferenceJid, true, true, true);
1052				bookmark.setConversation(conversation);
1053				dialog.dismiss();
1054				switchToConversation(conversation);
1055			}
1056		} else {
1057			final Conversation conversation = xmppConnectionService
1058					.findOrCreateConversation(account, conferenceJid, true, true, true);
1059			dialog.dismiss();
1060			switchToConversation(conversation);
1061		}
1062	}
1063
1064	@Override
1065	public void onConversationUpdate() {
1066		refreshUi();
1067	}
1068
1069	@Override
1070	public void onRefresh() {
1071		Log.d(Config.LOGTAG,"user requested to refresh");
1072		if (QuickConversationsService.isQuicksy() && xmppConnectionService != null) {
1073			xmppConnectionService.getQuickConversationsService().considerSyncBackground(true);
1074		}
1075	}
1076
1077
1078	private void setRefreshing(boolean refreshing) {
1079		MyListFragment fragment = (MyListFragment) mListPagerAdapter.getItem(0);
1080		if (fragment != null) {
1081			fragment.setRefreshing(refreshing);
1082		}
1083	}
1084
1085	@Override
1086	public void onCreatePublicChannel(Account account, String name, Jid address) {
1087		mToast = Toast.makeText(this, R.string.creating_channel, Toast.LENGTH_LONG);
1088		mToast.show();
1089		xmppConnectionService.createPublicChannel(account, name, address, new UiCallback<Conversation>() {
1090			@Override
1091			public void success(Conversation conversation) {
1092				runOnUiThread(() -> {
1093					hideToast();
1094					switchToConversation(conversation);
1095				});
1096
1097			}
1098
1099			@Override
1100			public void error(int errorCode, Conversation conversation) {
1101				runOnUiThread(() -> {
1102					replaceToast(getString(errorCode));
1103					switchToConversation(conversation);
1104				});
1105			}
1106
1107			@Override
1108			public void userInputRequired(PendingIntent pi, Conversation object) {
1109
1110			}
1111		});
1112	}
1113
1114	public static class MyListFragment extends SwipeRefreshListFragment {
1115		private AdapterView.OnItemClickListener mOnItemClickListener;
1116		private int mResContextMenu;
1117
1118		public void setContextMenu(final int res) {
1119			this.mResContextMenu = res;
1120		}
1121
1122		@Override
1123		public void onListItemClick(final ListView l, final View v, final int position, final long id) {
1124			if (mOnItemClickListener != null) {
1125				mOnItemClickListener.onItemClick(l, v, position, id);
1126			}
1127		}
1128
1129		public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
1130			this.mOnItemClickListener = l;
1131		}
1132
1133		@Override
1134		public void onViewCreated(@NonNull final View view, final Bundle savedInstanceState) {
1135			super.onViewCreated(view, savedInstanceState);
1136			registerForContextMenu(getListView());
1137			getListView().setFastScrollEnabled(true);
1138			getListView().setDivider(null);
1139			getListView().setDividerHeight(0);
1140		}
1141
1142		@Override
1143		public void onCreateContextMenu(final ContextMenu menu, final View v, final ContextMenuInfo menuInfo) {
1144			super.onCreateContextMenu(menu, v, menuInfo);
1145			final StartConversationActivity activity = (StartConversationActivity) getActivity();
1146			if (activity == null) {
1147				return;
1148			}
1149			activity.getMenuInflater().inflate(mResContextMenu, menu);
1150			final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
1151			if (mResContextMenu == R.menu.conference_context) {
1152				activity.conference_context_id = acmi.position;
1153				final Bookmark bookmark = (Bookmark) activity.conferences.get(acmi.position);
1154				final Conversation conversation = bookmark.getConversation();
1155				final MenuItem share = menu.findItem(R.id.context_share_uri);
1156				share.setVisible(conversation == null || !conversation.isPrivateAndNonAnonymous());
1157			} else if (mResContextMenu == R.menu.contact_context) {
1158				activity.contact_context_id = acmi.position;
1159				final Contact contact = (Contact) activity.contacts.get(acmi.position);
1160				final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
1161				final MenuItem showContactDetailsItem = menu.findItem(R.id.context_contact_details);
1162				final MenuItem deleteContactMenuItem = menu.findItem(R.id.context_delete_contact);
1163				if (contact.isSelf()) {
1164					showContactDetailsItem.setVisible(false);
1165				}
1166				deleteContactMenuItem.setVisible(contact.showInRoster() && !contact.getOption(Contact.Options.SYNCED_VIA_OTHER));
1167				final XmppConnection xmpp = contact.getAccount().getXmppConnection();
1168				if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) {
1169					if (contact.isBlocked()) {
1170						blockUnblockItem.setTitle(R.string.unblock_contact);
1171					} else {
1172						blockUnblockItem.setTitle(R.string.block_contact);
1173					}
1174				} else {
1175					blockUnblockItem.setVisible(false);
1176				}
1177			}
1178		}
1179
1180		@Override
1181		public boolean onContextItemSelected(final MenuItem item) {
1182			StartConversationActivity activity = (StartConversationActivity) getActivity();
1183			if (activity == null) {
1184				return true;
1185			}
1186			switch (item.getItemId()) {
1187				case R.id.context_contact_details:
1188					activity.openDetailsForContact();
1189					break;
1190				case R.id.context_show_qr:
1191					activity.showQrForContact();
1192					break;
1193				case R.id.context_contact_block_unblock:
1194					activity.toggleContactBlock();
1195					break;
1196				case R.id.context_delete_contact:
1197					activity.deleteContact();
1198					break;
1199				case R.id.context_share_uri:
1200					activity.shareBookmarkUri();
1201					break;
1202				case R.id.context_delete_conference:
1203					activity.deleteConference();
1204			}
1205			return true;
1206		}
1207	}
1208
1209	public class ListPagerAdapter extends PagerAdapter {
1210		private final FragmentManager fragmentManager;
1211		private final MyListFragment[] fragments;
1212
1213		ListPagerAdapter(FragmentManager fm) {
1214			fragmentManager = fm;
1215			fragments = new MyListFragment[2];
1216		}
1217
1218		public void requestFocus(int pos) {
1219			if (fragments.length > pos) {
1220				fragments[pos].getListView().requestFocus();
1221			}
1222		}
1223
1224		@Override
1225		public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
1226			FragmentTransaction trans = fragmentManager.beginTransaction();
1227			trans.remove(fragments[position]);
1228			trans.commit();
1229			fragments[position] = null;
1230		}
1231
1232		@NonNull
1233		@Override
1234		public Fragment instantiateItem(@NonNull ViewGroup container, int position) {
1235			final Fragment fragment = getItem(position);
1236			final FragmentTransaction trans = fragmentManager.beginTransaction();
1237			trans.add(container.getId(), fragment, "fragment:" + position);
1238			try {
1239				trans.commit();
1240			} catch (IllegalStateException e) {
1241				//ignore
1242			}
1243			return fragment;
1244		}
1245
1246		@Override
1247		public int getCount() {
1248			return fragments.length;
1249		}
1250
1251		@Override
1252		public boolean isViewFromObject(@NonNull View view, @NonNull Object fragment) {
1253			return ((Fragment) fragment).getView() == view;
1254		}
1255
1256		@Nullable
1257		@Override
1258		public CharSequence getPageTitle(int position) {
1259			switch (position) {
1260				case 0:
1261					return getResources().getString(R.string.contacts);
1262				case 1:
1263					return getResources().getString(R.string.bookmarks);
1264				default:
1265					return super.getPageTitle(position);
1266			}
1267		}
1268
1269		Fragment getItem(int position) {
1270			if (fragments[position] == null) {
1271				final MyListFragment listFragment = new MyListFragment();
1272				if (position == 1) {
1273					listFragment.setListAdapter(mConferenceAdapter);
1274					listFragment.setContextMenu(R.menu.conference_context);
1275					listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForBookmark(p));
1276				} else {
1277					listFragment.setListAdapter(mContactsAdapter);
1278					listFragment.setContextMenu(R.menu.contact_context);
1279					listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForContact(p));
1280					if (QuickConversationsService.isQuicksy()) {
1281						listFragment.setOnRefreshListener(StartConversationActivity.this);
1282					}
1283				}
1284				fragments[position] = listFragment;
1285			}
1286			return fragments[position];
1287		}
1288	}
1289
1290	public static void addInviteUri(Intent to, Intent from) {
1291		if (from != null && from.hasExtra(EXTRA_INVITE_URI)) {
1292			final String invite = from.getStringExtra(EXTRA_INVITE_URI);
1293			to.putExtra(EXTRA_INVITE_URI, invite);
1294		}
1295	}
1296
1297	private class Invite extends XmppUri {
1298
1299		public String account;
1300
1301		boolean forceDialog = false;
1302
1303
1304		Invite(final String uri) {
1305			super(uri);
1306		}
1307
1308		Invite(Uri uri, boolean safeSource) {
1309			super(uri, safeSource);
1310		}
1311
1312		boolean invite() {
1313			if (!isValidJid()) {
1314				Toast.makeText(StartConversationActivity.this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
1315				return false;
1316			}
1317			if (getJid() != null) {
1318				return handleJid(this);
1319			}
1320			return false;
1321		}
1322	}
1323}