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 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
736 final AtomicBoolean requestPermission = new AtomicBoolean(false);
737 builder.setTitle(R.string.sync_with_contacts);
738 if (QuickConversationsService.isQuicksy()) {
739 builder.setMessage(Html.fromHtml(getString(R.string.sync_with_contacts_quicksy)));
740 } else {
741 builder.setMessage(getString(R.string.sync_with_contacts_long, getString(R.string.app_name)));
742 }
743 builder.setPositiveButton(R.string.next, (dialog, which) -> {
744 if (requestPermission.compareAndSet(false, true)) {
745 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
746 }
747 });
748 builder.setOnDismissListener(dialog -> {
749 if (requestPermission.compareAndSet(false, true)) {
750 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
751
752 }
753 });
754 builder.setCancelable(false);
755 final AlertDialog dialog = builder.create();
756 dialog.setCanceledOnTouchOutside(false);
757 dialog.setOnShowListener(dialogInterface -> {
758 final TextView tv = dialog.findViewById(android.R.id.message);
759 if (tv != null) {
760 tv.setMovementMethod(LinkMovementMethod.getInstance());
761 }
762 });
763 dialog.show();
764 } else {
765 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
766 }
767 }
768 }
769 }
770 }
771
772 @Override
773 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
774 if (grantResults.length > 0)
775 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
776 ScanActivity.onRequestPermissionResult(this, requestCode, grantResults);
777 if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
778 if (QuickConversationsService.isQuicksy()) {
779 setRefreshing(true);
780 }
781 xmppConnectionService.loadPhoneContacts();
782 xmppConnectionService.startContactObserver();
783 }
784 }
785 }
786
787 private void configureHomeButton() {
788 final ActionBar actionBar = getSupportActionBar();
789 if (actionBar == null) {
790 return;
791 }
792 boolean openConversations = !createdByViewIntent && !xmppConnectionService.isConversationsListEmpty(null);
793 actionBar.setDisplayHomeAsUpEnabled(openConversations);
794 actionBar.setDisplayHomeAsUpEnabled(openConversations);
795
796 }
797
798 @Override
799 protected void onBackendConnected() {
800
801 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
802 xmppConnectionService.getQuickConversationsService().considerSyncBackground(false);
803 }
804 if (mPostponedActivityResult != null) {
805 onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
806 this.mPostponedActivityResult = null;
807 }
808 this.mActivatedAccounts.clear();
809 this.mActivatedAccounts.addAll(AccountUtils.getEnabledAccounts(xmppConnectionService));
810 configureHomeButton();
811 Intent intent = pendingViewIntent.pop();
812 if (intent != null && processViewIntent(intent)) {
813 filter(null);
814 } else {
815 if (mSearchEditText != null) {
816 filter(mSearchEditText.getText().toString());
817 } else {
818 filter(null);
819 }
820 }
821 Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
822 if (fragment instanceof OnBackendConnected) {
823 Log.d(Config.LOGTAG, "calling on backend connected on dialog");
824 ((OnBackendConnected) fragment).onBackendConnected();
825 }
826 if (QuickConversationsService.isQuicksy()) {
827 setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing());
828 }
829 if (QuickConversationsService.isConversations() && AccountUtils.hasEnabledAccounts(xmppConnectionService) && this.contacts.size() == 0 && this.conferences.size() == 0 && mOpenedFab.compareAndSet(false, true)) {
830 binding.speedDial.open();
831 }
832 }
833
834 protected boolean processViewIntent(@NonNull Intent intent) {
835 final String inviteUri = intent.getStringExtra(EXTRA_INVITE_URI);
836 if (inviteUri != null) {
837 final Invite invite = new Invite(inviteUri);
838 invite.account = intent.getStringExtra(EXTRA_ACCOUNT);
839 if (invite.isValidJid()) {
840 return invite.invite();
841 }
842 }
843 final String action = intent.getAction();
844 if (action == null) {
845 return false;
846 }
847 switch (action) {
848 case Intent.ACTION_SENDTO:
849 case Intent.ACTION_VIEW:
850 Uri uri = intent.getData();
851 if (uri != null) {
852 Invite invite = new Invite(intent.getData(), intent.getBooleanExtra("scanned", false));
853 invite.account = intent.getStringExtra(EXTRA_ACCOUNT);
854 invite.forceDialog = intent.getBooleanExtra("force_dialog", false);
855 return invite.invite();
856 } else {
857 return false;
858 }
859 }
860 return false;
861 }
862
863 private boolean handleJid(Invite invite) {
864 List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid(), invite.account);
865 if (invite.isAction(XmppUri.ACTION_JOIN)) {
866 Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
867 if (muc != null && !invite.forceDialog) {
868 switchToConversationDoNotAppend(muc, invite.getBody());
869 return true;
870 } else {
871 showJoinConferenceDialog(invite.getJid().asBareJid().toEscapedString());
872 return false;
873 }
874 } else if (contacts.size() == 0) {
875 showCreateContactDialog(invite.getJid().toEscapedString(), invite);
876 return false;
877 } else if (contacts.size() == 1) {
878 Contact contact = contacts.get(0);
879 if (!invite.isSafeSource() && invite.hasFingerprints()) {
880 displayVerificationWarningDialog(contact, invite);
881 } else {
882 if (invite.hasFingerprints()) {
883 if (xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints())) {
884 Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
885 }
886 }
887 if (invite.account != null) {
888 xmppConnectionService.getShortcutService().report(contact);
889 }
890 switchToConversationDoNotAppend(contact, invite.getBody());
891 }
892 return true;
893 } else {
894 if (mMenuSearchView != null) {
895 mMenuSearchView.expandActionView();
896 mSearchEditText.setText("");
897 mSearchEditText.append(invite.getJid().toEscapedString());
898 filter(invite.getJid().toEscapedString());
899 } else {
900 mInitialSearchValue.push(invite.getJid().toEscapedString());
901 }
902 return true;
903 }
904 }
905
906 private void displayVerificationWarningDialog(final Contact contact, final Invite invite) {
907 AlertDialog.Builder builder = new AlertDialog.Builder(this);
908 builder.setTitle(R.string.verify_omemo_keys);
909 View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
910 final CheckBox isTrustedSource = view.findViewById(R.id.trusted_source);
911 TextView warning = view.findViewById(R.id.warning);
912 warning.setText(JidDialog.style(this, R.string.verifying_omemo_keys_trusted_source, contact.getJid().asBareJid().toEscapedString(), contact.getDisplayName()));
913 builder.setView(view);
914 builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
915 if (isTrustedSource.isChecked() && invite.hasFingerprints()) {
916 xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
917 }
918 switchToConversationDoNotAppend(contact, invite.getBody());
919 });
920 builder.setNegativeButton(R.string.cancel, (dialog, which) -> StartConversationActivity.this.finish());
921 AlertDialog dialog = builder.create();
922 dialog.setCanceledOnTouchOutside(false);
923 dialog.setOnCancelListener(dialog1 -> StartConversationActivity.this.finish());
924 dialog.show();
925 }
926
927 protected void filter(String needle) {
928 if (xmppConnectionServiceBound) {
929 this.filterContacts(needle);
930 this.filterConferences(needle);
931 }
932 }
933
934 protected void filterContacts(String needle) {
935 this.contacts.clear();
936 final List<Account> accounts = xmppConnectionService.getAccounts();
937 for (Account account : accounts) {
938 if (account.getStatus() != Account.State.DISABLED) {
939 for (Contact contact : account.getRoster().getContacts()) {
940 Presence.Status s = contact.getShownStatus();
941 if (contact.showInContactList() && contact.match(this, needle)
942 && (!this.mHideOfflineContacts
943 || (needle != null && !needle.trim().isEmpty())
944 || s.compareTo(Presence.Status.OFFLINE) < 0)) {
945 this.contacts.add(contact);
946 }
947 }
948 }
949 }
950 Collections.sort(this.contacts);
951 mContactsAdapter.notifyDataSetChanged();
952 }
953
954 protected void filterConferences(String needle) {
955 this.conferences.clear();
956 for (Account account : xmppConnectionService.getAccounts()) {
957 if (account.getStatus() != Account.State.DISABLED) {
958 for (Bookmark bookmark : account.getBookmarks()) {
959 if (bookmark.match(this, needle)) {
960 this.conferences.add(bookmark);
961 }
962 }
963 }
964 }
965 Collections.sort(this.conferences);
966 mConferenceAdapter.notifyDataSetChanged();
967 }
968
969 @Override
970 public void OnUpdateBlocklist(final Status status) {
971 refreshUi();
972 }
973
974 @Override
975 protected void refreshUiReal() {
976 if (mSearchEditText != null) {
977 filter(mSearchEditText.getText().toString());
978 }
979 configureHomeButton();
980 if (QuickConversationsService.isQuicksy()) {
981 setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing());
982 }
983 }
984
985 @Override
986 public void onBackPressed() {
987 if (binding.speedDial.isOpen()) {
988 binding.speedDial.close();
989 return;
990 }
991 navigateBack();
992 }
993
994 private void navigateBack() {
995 if (!createdByViewIntent && xmppConnectionService != null && !xmppConnectionService.isConversationsListEmpty(null)) {
996 Intent intent = new Intent(this, ConversationsActivity.class);
997 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
998 startActivity(intent);
999 }
1000 finish();
1001 }
1002
1003 @Override
1004 public void onCreateDialogPositiveClick(Spinner spinner, String name) {
1005 if (!xmppConnectionServiceBound) {
1006 return;
1007 }
1008 final Account account = getSelectedAccount(this, spinner);
1009 if (account == null) {
1010 return;
1011 }
1012 Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class);
1013 intent.putExtra(ChooseContactActivity.EXTRA_SHOW_ENTER_JID, false);
1014 intent.putExtra(ChooseContactActivity.EXTRA_SELECT_MULTIPLE, true);
1015 intent.putExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME, name.trim());
1016 intent.putExtra(ChooseContactActivity.EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
1017 intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants);
1018 startActivityForResult(intent, REQUEST_CREATE_CONFERENCE);
1019 }
1020
1021 @Override
1022 public void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, TextInputLayout layout, AutoCompleteTextView jid, boolean isBookmarkChecked) {
1023 if (!xmppConnectionServiceBound) {
1024 return;
1025 }
1026 final Account account = getSelectedAccount(this, spinner);
1027 if (account == null) {
1028 return;
1029 }
1030 final String input = jid.getText().toString();
1031 Jid conferenceJid;
1032 try {
1033 conferenceJid = Jid.ofEscaped(input);
1034 } catch (final IllegalArgumentException e) {
1035 final XmppUri xmppUri = new XmppUri(input);
1036 if (xmppUri.isValidJid() && xmppUri.isAction(XmppUri.ACTION_JOIN)) {
1037 final Editable editable = jid.getEditableText();
1038 editable.clear();
1039 editable.append(xmppUri.getJid().toEscapedString());
1040 conferenceJid = xmppUri.getJid();
1041 } else {
1042 layout.setError(getString(R.string.invalid_jid));
1043 return;
1044 }
1045 }
1046
1047 if (isBookmarkChecked) {
1048 Bookmark bookmark = account.getBookmark(conferenceJid);
1049 if (bookmark != null) {
1050 dialog.dismiss();
1051 openConversationsForBookmark(bookmark);
1052 } else {
1053 bookmark = new Bookmark(account, conferenceJid.asBareJid());
1054 bookmark.setAutojoin(getBooleanPreference("autojoin", R.bool.autojoin));
1055 final String nick = conferenceJid.getResource();
1056 if (nick != null && !nick.isEmpty() && !nick.equals(MucOptions.defaultNick(account))) {
1057 bookmark.setNick(nick);
1058 }
1059 xmppConnectionService.createBookmark(account, bookmark);
1060 final Conversation conversation = xmppConnectionService
1061 .findOrCreateConversation(account, conferenceJid, true, true, true);
1062 bookmark.setConversation(conversation);
1063 dialog.dismiss();
1064 switchToConversation(conversation);
1065 }
1066 } else {
1067 final Conversation conversation = xmppConnectionService
1068 .findOrCreateConversation(account, conferenceJid, true, true, true);
1069 dialog.dismiss();
1070 switchToConversation(conversation);
1071 }
1072 }
1073
1074 @Override
1075 public void onConversationUpdate() {
1076 refreshUi();
1077 }
1078
1079 @Override
1080 public void onRefresh() {
1081 Log.d(Config.LOGTAG, "user requested to refresh");
1082 if (QuickConversationsService.isQuicksy() && xmppConnectionService != null) {
1083 xmppConnectionService.getQuickConversationsService().considerSyncBackground(true);
1084 }
1085 }
1086
1087
1088 private void setRefreshing(boolean refreshing) {
1089 MyListFragment fragment = (MyListFragment) mListPagerAdapter.getItem(0);
1090 if (fragment != null) {
1091 fragment.setRefreshing(refreshing);
1092 }
1093 }
1094
1095 @Override
1096 public void onCreatePublicChannel(Account account, String name, Jid address) {
1097 mToast = Toast.makeText(this, R.string.creating_channel, Toast.LENGTH_LONG);
1098 mToast.show();
1099 xmppConnectionService.createPublicChannel(account, name, address, new UiCallback<Conversation>() {
1100 @Override
1101 public void success(Conversation conversation) {
1102 runOnUiThread(() -> {
1103 hideToast();
1104 switchToConversation(conversation);
1105 });
1106
1107 }
1108
1109 @Override
1110 public void error(int errorCode, Conversation conversation) {
1111 runOnUiThread(() -> {
1112 replaceToast(getString(errorCode));
1113 switchToConversation(conversation);
1114 });
1115 }
1116
1117 @Override
1118 public void userInputRequired(PendingIntent pi, Conversation object) {
1119
1120 }
1121 });
1122 }
1123
1124 public static class MyListFragment extends SwipeRefreshListFragment {
1125 private AdapterView.OnItemClickListener mOnItemClickListener;
1126 private int mResContextMenu;
1127
1128 public void setContextMenu(final int res) {
1129 this.mResContextMenu = res;
1130 }
1131
1132 @Override
1133 public void onListItemClick(final ListView l, final View v, final int position, final long id) {
1134 if (mOnItemClickListener != null) {
1135 mOnItemClickListener.onItemClick(l, v, position, id);
1136 }
1137 }
1138
1139 public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
1140 this.mOnItemClickListener = l;
1141 }
1142
1143 @Override
1144 public void onViewCreated(@NonNull final View view, final Bundle savedInstanceState) {
1145 super.onViewCreated(view, savedInstanceState);
1146 registerForContextMenu(getListView());
1147 getListView().setFastScrollEnabled(true);
1148 getListView().setDivider(null);
1149 getListView().setDividerHeight(0);
1150 }
1151
1152 @Override
1153 public void onCreateContextMenu(final ContextMenu menu, final View v, final ContextMenuInfo menuInfo) {
1154 super.onCreateContextMenu(menu, v, menuInfo);
1155 final StartConversationActivity activity = (StartConversationActivity) getActivity();
1156 if (activity == null) {
1157 return;
1158 }
1159 activity.getMenuInflater().inflate(mResContextMenu, menu);
1160 final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
1161 if (mResContextMenu == R.menu.conference_context) {
1162 activity.conference_context_id = acmi.position;
1163 final Bookmark bookmark = (Bookmark) activity.conferences.get(acmi.position);
1164 final Conversation conversation = bookmark.getConversation();
1165 final MenuItem share = menu.findItem(R.id.context_share_uri);
1166 share.setVisible(conversation == null || !conversation.isPrivateAndNonAnonymous());
1167 } else if (mResContextMenu == R.menu.contact_context) {
1168 activity.contact_context_id = acmi.position;
1169 final Contact contact = (Contact) activity.contacts.get(acmi.position);
1170 final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
1171 final MenuItem showContactDetailsItem = menu.findItem(R.id.context_contact_details);
1172 final MenuItem deleteContactMenuItem = menu.findItem(R.id.context_delete_contact);
1173 if (contact.isSelf()) {
1174 showContactDetailsItem.setVisible(false);
1175 }
1176 deleteContactMenuItem.setVisible(contact.showInRoster() && !contact.getOption(Contact.Options.SYNCED_VIA_OTHER));
1177 final XmppConnection xmpp = contact.getAccount().getXmppConnection();
1178 if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) {
1179 if (contact.isBlocked()) {
1180 blockUnblockItem.setTitle(R.string.unblock_contact);
1181 } else {
1182 blockUnblockItem.setTitle(R.string.block_contact);
1183 }
1184 } else {
1185 blockUnblockItem.setVisible(false);
1186 }
1187 }
1188 }
1189
1190 @Override
1191 public boolean onContextItemSelected(final MenuItem item) {
1192 StartConversationActivity activity = (StartConversationActivity) getActivity();
1193 if (activity == null) {
1194 return true;
1195 }
1196 switch (item.getItemId()) {
1197 case R.id.context_contact_details:
1198 activity.openDetailsForContact();
1199 break;
1200 case R.id.context_show_qr:
1201 activity.showQrForContact();
1202 break;
1203 case R.id.context_contact_block_unblock:
1204 activity.toggleContactBlock();
1205 break;
1206 case R.id.context_delete_contact:
1207 activity.deleteContact();
1208 break;
1209 case R.id.context_share_uri:
1210 activity.shareBookmarkUri();
1211 break;
1212 case R.id.context_delete_conference:
1213 activity.deleteConference();
1214 }
1215 return true;
1216 }
1217 }
1218
1219 public class ListPagerAdapter extends PagerAdapter {
1220 private final FragmentManager fragmentManager;
1221 private final MyListFragment[] fragments;
1222
1223 ListPagerAdapter(FragmentManager fm) {
1224 fragmentManager = fm;
1225 fragments = new MyListFragment[2];
1226 }
1227
1228 public void requestFocus(int pos) {
1229 if (fragments.length > pos) {
1230 fragments[pos].getListView().requestFocus();
1231 }
1232 }
1233
1234 @Override
1235 public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
1236 FragmentTransaction trans = fragmentManager.beginTransaction();
1237 trans.remove(fragments[position]);
1238 trans.commit();
1239 fragments[position] = null;
1240 }
1241
1242 @NonNull
1243 @Override
1244 public Fragment instantiateItem(@NonNull ViewGroup container, int position) {
1245 final Fragment fragment = getItem(position);
1246 final FragmentTransaction trans = fragmentManager.beginTransaction();
1247 trans.add(container.getId(), fragment, "fragment:" + position);
1248 try {
1249 trans.commit();
1250 } catch (IllegalStateException e) {
1251 //ignore
1252 }
1253 return fragment;
1254 }
1255
1256 @Override
1257 public int getCount() {
1258 return fragments.length;
1259 }
1260
1261 @Override
1262 public boolean isViewFromObject(@NonNull View view, @NonNull Object fragment) {
1263 return ((Fragment) fragment).getView() == view;
1264 }
1265
1266 @Nullable
1267 @Override
1268 public CharSequence getPageTitle(int position) {
1269 switch (position) {
1270 case 0:
1271 return getResources().getString(R.string.contacts);
1272 case 1:
1273 return getResources().getString(R.string.bookmarks);
1274 default:
1275 return super.getPageTitle(position);
1276 }
1277 }
1278
1279 Fragment getItem(int position) {
1280 if (fragments[position] == null) {
1281 final MyListFragment listFragment = new MyListFragment();
1282 if (position == 1) {
1283 listFragment.setListAdapter(mConferenceAdapter);
1284 listFragment.setContextMenu(R.menu.conference_context);
1285 listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForBookmark(p));
1286 } else {
1287 listFragment.setListAdapter(mContactsAdapter);
1288 listFragment.setContextMenu(R.menu.contact_context);
1289 listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForContact(p));
1290 if (QuickConversationsService.isQuicksy()) {
1291 listFragment.setOnRefreshListener(StartConversationActivity.this);
1292 }
1293 }
1294 fragments[position] = listFragment;
1295 }
1296 return fragments[position];
1297 }
1298 }
1299
1300 public static void addInviteUri(Intent to, Intent from) {
1301 if (from != null && from.hasExtra(EXTRA_INVITE_URI)) {
1302 final String invite = from.getStringExtra(EXTRA_INVITE_URI);
1303 to.putExtra(EXTRA_INVITE_URI, invite);
1304 }
1305 }
1306
1307 private class Invite extends XmppUri {
1308
1309 public String account;
1310
1311 boolean forceDialog = false;
1312
1313
1314 Invite(final String uri) {
1315 super(uri);
1316 }
1317
1318 Invite(Uri uri, boolean safeSource) {
1319 super(uri, safeSource);
1320 }
1321
1322 boolean invite() {
1323 if (!isValidJid()) {
1324 Toast.makeText(StartConversationActivity.this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
1325 return false;
1326 }
1327 if (getJid() != null) {
1328 return handleJid(this);
1329 }
1330 return false;
1331 }
1332 }
1333}