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