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