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