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