1package eu.siacs.conversations.ui;
2
3import android.Manifest;
4import android.annotation.SuppressLint;
5import android.support.v7.app.AlertDialog;
6import android.app.Dialog;
7import android.app.PendingIntent;
8import android.content.ActivityNotFoundException;
9import android.content.Context;
10import android.content.DialogInterface;
11import android.content.DialogInterface.OnClickListener;
12import android.content.Intent;
13import android.content.pm.PackageManager;
14import android.net.Uri;
15import android.os.Build;
16import android.os.Bundle;
17import android.support.v4.app.Fragment;
18import android.support.v4.app.FragmentManager;
19import android.support.v4.app.FragmentTransaction;
20import android.support.v4.app.ListFragment;
21import android.support.v4.view.MenuItemCompat;
22import android.support.v4.view.PagerAdapter;
23import android.support.v4.view.ViewPager;
24import android.support.v7.app.ActionBar;
25import android.text.Editable;
26import android.text.SpannableString;
27import android.text.Spanned;
28import android.text.TextWatcher;
29import android.text.style.TypefaceSpan;
30import android.util.Pair;
31import android.view.ContextMenu;
32import android.view.ContextMenu.ContextMenuInfo;
33import android.view.KeyEvent;
34import android.view.Menu;
35import android.view.MenuItem;
36import android.view.View;
37import android.view.ViewGroup;
38import android.view.inputmethod.InputMethodManager;
39import android.widget.AdapterView;
40import android.widget.AdapterView.AdapterContextMenuInfo;
41import android.widget.AdapterView.OnItemClickListener;
42import android.widget.ArrayAdapter;
43import android.widget.AutoCompleteTextView;
44import android.widget.CheckBox;
45import android.widget.Checkable;
46import android.widget.EditText;
47import android.widget.ListView;
48import android.widget.Spinner;
49import android.widget.TextView;
50import android.widget.Toast;
51
52import java.util.ArrayList;
53import java.util.Arrays;
54import java.util.Collections;
55import java.util.List;
56import java.util.concurrent.atomic.AtomicBoolean;
57
58import eu.siacs.conversations.Config;
59import eu.siacs.conversations.R;
60import eu.siacs.conversations.entities.Account;
61import eu.siacs.conversations.entities.Bookmark;
62import eu.siacs.conversations.entities.Contact;
63import eu.siacs.conversations.entities.Conversation;
64import eu.siacs.conversations.entities.ListItem;
65import eu.siacs.conversations.entities.Presence;
66import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
67import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
68import eu.siacs.conversations.ui.adapter.ListItemAdapter;
69import eu.siacs.conversations.ui.service.EmojiService;
70import eu.siacs.conversations.utils.XmppUri;
71import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
72import eu.siacs.conversations.xmpp.XmppConnection;
73import eu.siacs.conversations.xmpp.jid.InvalidJidException;
74import eu.siacs.conversations.xmpp.jid.Jid;
75
76public class StartConversationActivity extends XmppActivity implements OnRosterUpdate, OnUpdateBlocklist {
77
78 public int conference_context_id;
79 public int contact_context_id;
80 private ActionBar.Tab mContactsTab;
81 private ActionBar.Tab mConferencesTab;
82 private ViewPager mViewPager;
83 private ListPagerAdapter mListPagerAdapter;
84 private List<ListItem> contacts = new ArrayList<>();
85 private ListItemAdapter mContactsAdapter;
86 private List<ListItem> conferences = new ArrayList<>();
87 private ListItemAdapter mConferenceAdapter;
88 private List<String> mActivatedAccounts = new ArrayList<>();
89 private List<String> mKnownHosts;
90 private List<String> mKnownConferenceHosts;
91 private Invite mPendingInvite = null;
92 private EditText mSearchEditText;
93 private AtomicBoolean mRequestedContactsPermission = new AtomicBoolean(false);
94 private final int REQUEST_SYNC_CONTACTS = 0x3b28cf;
95 private final int REQUEST_CREATE_CONFERENCE = 0x3b39da;
96 private Dialog mCurrentDialog = null;
97
98 private MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
99
100 @Override
101 public boolean onMenuItemActionExpand(MenuItem item) {
102 mSearchEditText.post(() -> {
103 mSearchEditText.requestFocus();
104 InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
105 imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
106 });
107
108 return true;
109 }
110
111 @Override
112 public boolean onMenuItemActionCollapse(MenuItem item) {
113 hideKeyboard();
114 mSearchEditText.setText("");
115 filter(null);
116 return true;
117 }
118 };
119 private boolean mHideOfflineContacts = false;
120 private ActionBar.TabListener mTabListener = new ActionBar.TabListener() {
121
122 @Override
123 public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
124 return;
125 }
126
127 @Override
128 public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
129 mViewPager.setCurrentItem(tab.getPosition());
130 onTabChanged();
131 }
132
133 @Override
134 public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
135 return;
136 }
137 };
138 private ViewPager.SimpleOnPageChangeListener mOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
139 @Override
140 public void onPageSelected(int position) {
141 ActionBar actionBar = getSupportActionBar();
142 if (actionBar != null) {
143 actionBar.setSelectedNavigationItem(position);
144 }
145 onTabChanged();
146 }
147 };
148 private TextWatcher mSearchTextWatcher = new TextWatcher() {
149
150 @Override
151 public void afterTextChanged(Editable editable) {
152 filter(editable.toString());
153 }
154
155 @Override
156 public void beforeTextChanged(CharSequence s, int start, int count,
157 int after) {
158 }
159
160 @Override
161 public void onTextChanged(CharSequence s, int start, int before, int count) {
162 }
163 };
164
165 private TextView.OnEditorActionListener mSearchDone = new TextView.OnEditorActionListener() {
166 @Override
167 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
168 int pos = getSupportActionBar().getSelectedNavigationIndex();
169 if (pos == 0) {
170 if (contacts.size() == 1) {
171 openConversationForContact((Contact) contacts.get(0));
172 return true;
173 }
174 } else {
175 if (conferences.size() == 1) {
176 openConversationsForBookmark((Bookmark) conferences.get(0));
177 return true;
178 }
179 }
180 hideKeyboard();
181 mListPagerAdapter.requestFocus(pos);
182 return true;
183 }
184 };
185 private MenuItem mMenuSearchView;
186 private ListItemAdapter.OnTagClickedListener mOnTagClickedListener = new ListItemAdapter.OnTagClickedListener() {
187 @Override
188 public void onTagClicked(String tag) {
189 if (mMenuSearchView != null) {
190 mMenuSearchView.expandActionView();
191 mSearchEditText.setText("");
192 mSearchEditText.append(tag);
193 filter(tag);
194 }
195 }
196 };
197 private String mInitialJid;
198 private Pair<Integer, Intent> mPostponedActivityResult;
199 private UiCallback<Conversation> mAdhocConferenceCallback = new UiCallback<Conversation>() {
200 @Override
201 public void success(final Conversation conversation) {
202 runOnUiThread(() -> {
203 hideToast();
204 switchToConversation(conversation);
205 });
206 }
207
208 @Override
209 public void error(final int errorCode, Conversation object) {
210 runOnUiThread(() -> replaceToast(getString(errorCode)));
211 }
212
213 @Override
214 public void userInputRequried(PendingIntent pi, Conversation object) {
215
216 }
217 };
218 private Toast mToast;
219
220 protected void hideToast() {
221 if (mToast != null) {
222 mToast.cancel();
223 }
224 }
225
226 protected void replaceToast(String msg) {
227 hideToast();
228 mToast = Toast.makeText(this, msg, Toast.LENGTH_LONG);
229 mToast.show();
230 }
231
232 @Override
233 public void onRosterUpdate() {
234 this.refreshUi();
235 }
236
237 @Override
238 public void onCreate(Bundle savedInstanceState) {
239 super.onCreate(savedInstanceState);
240 new EmojiService(this).init();
241 setContentView(R.layout.activity_start_conversation);
242 mViewPager = findViewById(R.id.start_conversation_view_pager);
243 ActionBar actionBar = getSupportActionBar();
244 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
245
246 mContactsTab = actionBar.newTab().setText(R.string.contacts).setTabListener(mTabListener);
247 mConferencesTab = actionBar.newTab().setText(R.string.conferences).setTabListener(mTabListener);
248 actionBar.addTab(mContactsTab);
249 actionBar.addTab(mConferencesTab);
250
251 mViewPager.setOnPageChangeListener(mOnPageChangeListener);
252 mListPagerAdapter = new ListPagerAdapter(getSupportFragmentManager());
253 mViewPager.setAdapter(mListPagerAdapter);
254
255 mConferenceAdapter = new ListItemAdapter(this, conferences);
256 mContactsAdapter = new ListItemAdapter(this, contacts);
257 mContactsAdapter.setOnTagClickedListener(this.mOnTagClickedListener);
258 this.mHideOfflineContacts = getPreferences().getBoolean("hide_offline", false);
259
260 }
261
262 @Override
263 public void onStart() {
264 super.onStart();
265 final int theme = findTheme();
266 if (this.mTheme != theme) {
267 recreate();
268 } else {
269 Intent i = getIntent();
270 if (i == null || !i.hasExtra(WelcomeActivity.EXTRA_INVITE_URI)) {
271 askForContactsPermissions();
272 }
273 }
274 mConferenceAdapter.refreshSettings();
275 mContactsAdapter.refreshSettings();
276 }
277
278 @Override
279 public void onStop() {
280 if (mCurrentDialog != null) {
281 mCurrentDialog.dismiss();
282 }
283 super.onStop();
284 }
285
286 @Override
287 public void onNewIntent(Intent intent) {
288 if (xmppConnectionServiceBound) {
289 handleIntent(intent);
290 } else {
291 setIntent(intent);
292 }
293 }
294
295 protected void openConversationForContact(int position) {
296 Contact contact = (Contact) contacts.get(position);
297 openConversationForContact(contact);
298 }
299
300 protected void openConversationForContact(Contact contact) {
301 Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
302 switchToConversation(conversation);
303 }
304
305 protected void openConversationForContact() {
306 int position = contact_context_id;
307 openConversationForContact(position);
308 }
309
310 protected void openConversationForBookmark() {
311 openConversationForBookmark(conference_context_id);
312 }
313
314 protected void openConversationForBookmark(int position) {
315 Bookmark bookmark = (Bookmark) conferences.get(position);
316 openConversationsForBookmark(bookmark);
317 }
318
319 protected void shareBookmarkUri() {
320 shareBookmarkUri(conference_context_id);
321 }
322
323 protected void shareBookmarkUri(int position) {
324 Bookmark bookmark = (Bookmark) conferences.get(position);
325 Intent shareIntent = new Intent();
326 shareIntent.setAction(Intent.ACTION_SEND);
327 shareIntent.putExtra(Intent.EXTRA_TEXT, "xmpp:"+bookmark.getJid().toBareJid().toString()+"?join");
328 shareIntent.setType("text/plain");
329 try {
330 startActivity(Intent.createChooser(shareIntent, getText(R.string.share_uri_with)));
331 } catch (ActivityNotFoundException e) {
332 Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
333 }
334 }
335
336 protected void openConversationsForBookmark(Bookmark bookmark) {
337 Jid jid = bookmark.getJid();
338 if (jid == null) {
339 Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
340 return;
341 }
342 Conversation conversation = xmppConnectionService.findOrCreateConversation(bookmark.getAccount(), jid, true, true, true);
343 bookmark.setConversation(conversation);
344 if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", getResources().getBoolean(R.bool.autojoin))) {
345 bookmark.setAutojoin(true);
346 xmppConnectionService.pushBookmarks(bookmark.getAccount());
347 }
348 switchToConversation(conversation);
349 }
350
351 protected void openDetailsForContact() {
352 int position = contact_context_id;
353 Contact contact = (Contact) contacts.get(position);
354 switchToContactDetails(contact);
355 }
356
357 protected void toggleContactBlock() {
358 final int position = contact_context_id;
359 BlockContactDialog.show(this, (Contact) contacts.get(position));
360 }
361
362 protected void deleteContact() {
363 final int position = contact_context_id;
364 final Contact contact = (Contact) contacts.get(position);
365 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
366 builder.setNegativeButton(R.string.cancel, null);
367 builder.setTitle(R.string.action_delete_contact);
368 builder.setMessage(getString(R.string.remove_contact_text,
369 contact.getJid()));
370 builder.setPositiveButton(R.string.delete, new OnClickListener() {
371
372 @Override
373 public void onClick(DialogInterface dialog, int which) {
374 xmppConnectionService.deleteContactOnServer(contact);
375 filter(mSearchEditText.getText().toString());
376 }
377 });
378 builder.create().show();
379 }
380
381 protected void deleteConference() {
382 int position = conference_context_id;
383 final Bookmark bookmark = (Bookmark) conferences.get(position);
384
385 AlertDialog.Builder builder = new AlertDialog.Builder(this);
386 builder.setNegativeButton(R.string.cancel, null);
387 builder.setTitle(R.string.delete_bookmark);
388 builder.setMessage(getString(R.string.remove_bookmark_text,
389 bookmark.getJid()));
390 builder.setPositiveButton(R.string.delete, new OnClickListener() {
391
392 @Override
393 public void onClick(DialogInterface dialog, int which) {
394 bookmark.setConversation(null);
395 Account account = bookmark.getAccount();
396 account.getBookmarks().remove(bookmark);
397 xmppConnectionService.pushBookmarks(account);
398 filter(mSearchEditText.getText().toString());
399 }
400 });
401 builder.create().show();
402
403 }
404
405 @SuppressLint("InflateParams")
406 protected void showCreateContactDialog(final String prefilledJid, final Invite invite) {
407 EnterJidDialog dialog = new EnterJidDialog(
408 this, mKnownHosts, mActivatedAccounts,
409 getString(R.string.dialog_title_create_contact), getString(R.string.create),
410 prefilledJid, null, invite == null || !invite.hasFingerprints()
411 );
412
413 dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> {
414 if (!xmppConnectionServiceBound) {
415 return false;
416 }
417
418 final Account account = xmppConnectionService.findAccountByJid(accountJid);
419 if (account == null) {
420 return true;
421 }
422
423 final Contact contact = account.getRoster().getContact(contactJid);
424 if (invite != null && invite.getName() != null) {
425 contact.setServerName(invite.getName());
426 }
427 if (contact.isSelf()) {
428 switchToConversation(contact,null);
429 return true;
430 } else if (contact.showInRoster()) {
431 throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists));
432 } else {
433 xmppConnectionService.createContact(contact);
434 if (invite != null && invite.hasFingerprints()) {
435 xmppConnectionService.verifyFingerprints(contact,invite.getFingerprints());
436 }
437 switchToConversation(contact, invite == null ? null : invite.getBody());
438 return true;
439 }
440 });
441
442 mCurrentDialog = dialog.show();
443 }
444
445 @SuppressLint("InflateParams")
446 protected void showJoinConferenceDialog(final String prefilledJid) {
447 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
448 builder.setTitle(R.string.dialog_title_join_conference);
449 final View dialogView = getLayoutInflater().inflate(R.layout.join_conference_dialog, null);
450 final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
451 final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid);
452 final TextView jabberIdDesc = (TextView) dialogView.findViewById(R.id.jabber_id);
453 jabberIdDesc.setText(R.string.conference_address);
454 jid.setHint(R.string.conference_address_example);
455 jid.setAdapter(new KnownHostsAdapter(this, R.layout.simple_list_item, mKnownConferenceHosts));
456 if (prefilledJid != null) {
457 jid.append(prefilledJid);
458 }
459 populateAccountSpinner(this, mActivatedAccounts, spinner);
460 final Checkable bookmarkCheckBox = (CheckBox) dialogView
461 .findViewById(R.id.bookmark);
462 builder.setView(dialogView);
463 builder.setNegativeButton(R.string.cancel, null);
464 builder.setPositiveButton(R.string.join, null);
465 final AlertDialog dialog = builder.create();
466 dialog.show();
467 mCurrentDialog = dialog;
468 dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
469 new View.OnClickListener() {
470
471 @Override
472 public void onClick(final View v) {
473 if (!xmppConnectionServiceBound) {
474 return;
475 }
476 final Account account = getSelectedAccount(spinner);
477 if (account == null) {
478 return;
479 }
480 final Jid conferenceJid;
481 try {
482 conferenceJid = Jid.fromString(jid.getText().toString());
483 } catch (final InvalidJidException e) {
484 jid.setError(getString(R.string.invalid_jid));
485 return;
486 }
487
488 if (bookmarkCheckBox.isChecked()) {
489 if (account.hasBookmarkFor(conferenceJid)) {
490 jid.setError(getString(R.string.bookmark_already_exists));
491 } else {
492 final Bookmark bookmark = new Bookmark(account, conferenceJid.toBareJid());
493 bookmark.setAutojoin(getPreferences().getBoolean("autojoin", getResources().getBoolean(R.bool.autojoin)));
494 String nick = conferenceJid.getResourcepart();
495 if (nick != null && !nick.isEmpty()) {
496 bookmark.setNick(nick);
497 }
498 account.getBookmarks().add(bookmark);
499 xmppConnectionService.pushBookmarks(account);
500 final Conversation conversation = xmppConnectionService
501 .findOrCreateConversation(account, conferenceJid, true, true, true);
502 bookmark.setConversation(conversation);
503 dialog.dismiss();
504 mCurrentDialog = null;
505 switchToConversation(conversation);
506 }
507 } else {
508 final Conversation conversation = xmppConnectionService
509 .findOrCreateConversation(account,conferenceJid, true, true, true);
510 dialog.dismiss();
511 mCurrentDialog = null;
512 switchToConversation(conversation);
513 }
514 }
515 });
516 }
517
518 private void showCreateConferenceDialog() {
519 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
520 builder.setTitle(R.string.dialog_title_create_conference);
521 final View dialogView = getLayoutInflater().inflate(R.layout.create_conference_dialog, null);
522 final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
523 final EditText subject = (EditText) dialogView.findViewById(R.id.subject);
524 populateAccountSpinner(this, mActivatedAccounts, spinner);
525 builder.setView(dialogView);
526 builder.setPositiveButton(R.string.choose_participants, new OnClickListener() {
527 @Override
528 public void onClick(DialogInterface dialog, int which) {
529 if (!xmppConnectionServiceBound) {
530 return;
531 }
532 final Account account = getSelectedAccount(spinner);
533 if (account == null) {
534 return;
535 }
536 Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class);
537 intent.putExtra("multiple", true);
538 intent.putExtra("show_enter_jid", true);
539 intent.putExtra("subject", subject.getText().toString());
540 intent.putExtra(EXTRA_ACCOUNT, account.getJid().toBareJid().toString());
541 intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants);
542 startActivityForResult(intent, REQUEST_CREATE_CONFERENCE);
543 }
544 });
545 builder.setNegativeButton(R.string.cancel, null);
546 mCurrentDialog = builder.create();
547 mCurrentDialog.show();
548 }
549
550 private Account getSelectedAccount(Spinner spinner) {
551 if (!spinner.isEnabled()) {
552 return null;
553 }
554 Jid jid;
555 try {
556 if (Config.DOMAIN_LOCK != null) {
557 jid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
558 } else {
559 jid = Jid.fromString((String) spinner.getSelectedItem());
560 }
561 } catch (final InvalidJidException e) {
562 return null;
563 }
564 return xmppConnectionService.findAccountByJid(jid);
565 }
566
567 protected void switchToConversation(Contact contact, String body) {
568 Conversation conversation = xmppConnectionService
569 .findOrCreateConversation(contact.getAccount(),
570 contact.getJid(),false,true);
571 switchToConversation(conversation, body, false);
572 }
573
574 public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) {
575 if (accounts.size() > 0) {
576 ArrayAdapter<String> adapter = new ArrayAdapter<>(context, R.layout.simple_list_item, accounts);
577 adapter.setDropDownViewResource(R.layout.simple_list_item);
578 spinner.setAdapter(adapter);
579 spinner.setEnabled(true);
580 } else {
581 ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
582 R.layout.simple_list_item,
583 Arrays.asList(context.getString(R.string.no_accounts)));
584 adapter.setDropDownViewResource(R.layout.simple_list_item);
585 spinner.setAdapter(adapter);
586 spinner.setEnabled(false);
587 }
588 }
589
590 @Override
591 public boolean onCreateOptionsMenu(Menu menu) {
592 getMenuInflater().inflate(R.menu.start_conversation, menu);
593 MenuItem menuCreateContact = menu.findItem(R.id.action_create_contact);
594 MenuItem menuCreateConference = menu.findItem(R.id.action_conference);
595 MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline);
596 menuHideOffline.setChecked(this.mHideOfflineContacts);
597 mMenuSearchView = menu.findItem(R.id.action_search);
598 mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
599 View mSearchView = mMenuSearchView.getActionView();
600 mSearchEditText = (EditText) mSearchView.findViewById(R.id.search_field);
601 mSearchEditText.addTextChangedListener(mSearchTextWatcher);
602 mSearchEditText.setOnEditorActionListener(mSearchDone);
603 if (getSupportActionBar().getSelectedNavigationIndex() == 0) {
604 menuCreateConference.setVisible(false);
605 } else {
606 menuCreateContact.setVisible(false);
607 }
608 if (mInitialJid != null) {
609 MenuItemCompat.expandActionView(mMenuSearchView);
610 mSearchEditText.append(mInitialJid);
611 filter(mInitialJid);
612 }
613 return super.onCreateOptionsMenu(menu);
614 }
615
616 @Override
617 public boolean onOptionsItemSelected(MenuItem item) {
618 switch (item.getItemId()) {
619 case R.id.action_create_contact:
620 showCreateContactDialog(null, null);
621 return true;
622 case R.id.action_join_conference:
623 showJoinConferenceDialog(null);
624 return true;
625 case R.id.action_create_conference:
626 showCreateConferenceDialog();
627 return true;
628 case R.id.action_scan_qr_code:
629 Intent intent = new Intent(this, UriHandlerActivity.class);
630 intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE);
631 intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
632 startActivity(intent);
633 return true;
634 case R.id.action_hide_offline:
635 mHideOfflineContacts = !item.isChecked();
636 getPreferences().edit().putBoolean("hide_offline", mHideOfflineContacts).commit();
637 if (mSearchEditText != null) {
638 filter(mSearchEditText.getText().toString());
639 }
640 invalidateOptionsMenu();
641 }
642 return super.onOptionsItemSelected(item);
643 }
644
645 @Override
646 public boolean onKeyUp(int keyCode, KeyEvent event) {
647 if (keyCode == KeyEvent.KEYCODE_SEARCH && !event.isLongPress()) {
648 openSearch();
649 return true;
650 }
651 int c = event.getUnicodeChar();
652 if (c > 32) {
653 if (mSearchEditText != null && !mSearchEditText.isFocused()) {
654 openSearch();
655 mSearchEditText.append(Character.toString((char) c));
656 return true;
657 }
658 }
659 return super.onKeyUp(keyCode, event);
660 }
661
662 private void openSearch() {
663 if (mMenuSearchView != null) {
664 mMenuSearchView.expandActionView();
665 }
666 }
667
668 @Override
669 public void onActivityResult(int requestCode, int resultCode, Intent intent) {
670 if (resultCode == RESULT_OK) {
671 if (xmppConnectionServiceBound) {
672 this.mPostponedActivityResult = null;
673 if (requestCode == REQUEST_CREATE_CONFERENCE) {
674 Account account = extractAccount(intent);
675 final String subject = intent.getStringExtra("subject");
676 List<Jid> jids = new ArrayList<>();
677 if (intent.getBooleanExtra("multiple", false)) {
678 String[] toAdd = intent.getStringArrayExtra("contacts");
679 for (String item : toAdd) {
680 try {
681 jids.add(Jid.fromString(item));
682 } catch (InvalidJidException e) {
683 //ignored
684 }
685 }
686 } else {
687 try {
688 jids.add(Jid.fromString(intent.getStringExtra("contact")));
689 } catch (Exception e) {
690 //ignored
691 }
692 }
693 if (account != null && jids.size() > 0) {
694 if (xmppConnectionService.createAdhocConference(account, subject, jids, mAdhocConferenceCallback)) {
695 mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
696 mToast.show();
697 }
698 }
699 }
700 } else {
701 this.mPostponedActivityResult = new Pair<>(requestCode, intent);
702 }
703 }
704 super.onActivityResult(requestCode, requestCode, intent);
705 }
706
707 private void askForContactsPermissions() {
708 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
709 if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
710 if (mRequestedContactsPermission.compareAndSet(false, true)) {
711 if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
712 AlertDialog.Builder builder = new AlertDialog.Builder(this);
713 builder.setTitle(R.string.sync_with_contacts);
714 builder.setMessage(R.string.sync_with_contacts_long);
715 builder.setPositiveButton(R.string.next, new OnClickListener() {
716 @Override
717 public void onClick(DialogInterface dialog, int which) {
718 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
719 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
720 }
721 }
722 });
723 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
724 builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
725 @Override
726 public void onDismiss(DialogInterface dialog) {
727 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
728 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
729 }
730 }
731 });
732 }
733 builder.create().show();
734 } else {
735 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 0);
736 }
737 }
738 }
739 }
740 }
741
742 @Override
743 public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
744 if (grantResults.length > 0)
745 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
746 if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
747 xmppConnectionService.loadPhoneContacts();
748 }
749 }
750 }
751
752 @Override
753 protected void onBackendConnected() {
754 if (mPostponedActivityResult != null) {
755 onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
756 this.mPostponedActivityResult = null;
757 }
758 this.mActivatedAccounts.clear();
759 for (Account account : xmppConnectionService.getAccounts()) {
760 if (account.getStatus() != Account.State.DISABLED) {
761 if (Config.DOMAIN_LOCK != null) {
762 this.mActivatedAccounts.add(account.getJid().getLocalpart());
763 } else {
764 this.mActivatedAccounts.add(account.getJid().toBareJid().toString());
765 }
766 }
767 }
768 final Intent intent = getIntent();
769 final ActionBar ab = getSupportActionBar();
770 boolean init = intent != null && intent.getBooleanExtra("init", false);
771 boolean noConversations = xmppConnectionService.getConversations().size() == 0;
772 if ((init || noConversations) && ab != null) {
773 ab.setDisplayShowHomeEnabled(false);
774 ab.setDisplayHomeAsUpEnabled(false);
775 ab.setHomeButtonEnabled(false);
776 }
777 this.mKnownHosts = xmppConnectionService.getKnownHosts();
778 this.mKnownConferenceHosts = xmppConnectionService.getKnownConferenceHosts();
779 if (this.mPendingInvite != null) {
780 mPendingInvite.invite();
781 this.mPendingInvite = null;
782 filter(null);
783 } else if (!handleIntent(getIntent())) {
784 if (mSearchEditText != null) {
785 filter(mSearchEditText.getText().toString());
786 } else {
787 filter(null);
788 }
789 } else {
790 filter(null);
791 }
792 setIntent(null);
793 }
794
795 protected boolean handleIntent(Intent intent) {
796 if (intent == null) {
797 return false;
798 }
799 final String inviteUri = intent.getStringExtra(WelcomeActivity.EXTRA_INVITE_URI);
800 if (inviteUri != null) {
801 Invite invite = new Invite(inviteUri);
802 if (invite.isJidValid()) {
803 return invite.invite();
804 }
805 }
806 if (intent.getAction() == null) {
807 return false;
808 }
809 switch (intent.getAction()) {
810 case Intent.ACTION_SENDTO:
811 case Intent.ACTION_VIEW:
812 Uri uri = intent.getData();
813 if (uri != null) {
814 Invite invite = new Invite(intent.getData(),false);
815 invite.account = intent.getStringExtra("account");
816 return invite.invite();
817 } else {
818 return false;
819 }
820 }
821 return false;
822 }
823
824 private boolean handleJid(Invite invite) {
825 List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid(),invite.account);
826 if (invite.isAction(XmppUri.ACTION_JOIN)) {
827 Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
828 if (muc != null) {
829 switchToConversation(muc,invite.getBody(),false);
830 return true;
831 } else {
832 showJoinConferenceDialog(invite.getJid().toBareJid().toString());
833 return false;
834 }
835 } else if (contacts.size() == 0) {
836 showCreateContactDialog(invite.getJid().toString(), invite);
837 return false;
838 } else if (contacts.size() == 1) {
839 Contact contact = contacts.get(0);
840 if (!invite.isSafeSource() && invite.hasFingerprints()) {
841 displayVerificationWarningDialog(contact,invite);
842 } else {
843 if (invite.hasFingerprints()) {
844 if(xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints())) {
845 Toast.makeText(this,R.string.verified_fingerprints,Toast.LENGTH_SHORT).show();
846 }
847 }
848 if (invite.account != null) {
849 xmppConnectionService.getShortcutService().report(contact);
850 }
851 switchToConversation(contact, invite.getBody());
852 }
853 return true;
854 } else {
855 if (mMenuSearchView != null) {
856 mMenuSearchView.expandActionView();
857 mSearchEditText.setText("");
858 mSearchEditText.append(invite.getJid().toString());
859 filter(invite.getJid().toString());
860 } else {
861 mInitialJid = invite.getJid().toString();
862 }
863 return true;
864 }
865 }
866
867 private void displayVerificationWarningDialog(final Contact contact, final Invite invite) {
868 AlertDialog.Builder builder = new AlertDialog.Builder(this);
869 builder.setTitle(R.string.verify_omemo_keys);
870 View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
871 final CheckBox isTrustedSource = (CheckBox) view.findViewById(R.id.trusted_source);
872 TextView warning = (TextView) view.findViewById(R.id.warning);
873 String jid = contact.getJid().toBareJid().toString();
874 SpannableString spannable = new SpannableString(getString(R.string.verifying_omemo_keys_trusted_source,jid,contact.getDisplayName()));
875 int start = spannable.toString().indexOf(jid);
876 if (start >= 0) {
877 spannable.setSpan(new TypefaceSpan("monospace"),start,start + jid.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
878 }
879 warning.setText(spannable);
880 builder.setView(view);
881 builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
882 if (isTrustedSource.isChecked() && invite.hasFingerprints()) {
883 xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
884 }
885 switchToConversation(contact, invite.getBody());
886 });
887 builder.setNegativeButton(R.string.cancel, (dialog, which) -> StartConversationActivity.this.finish());
888 AlertDialog dialog = builder.create();
889 dialog.setCanceledOnTouchOutside(false);
890 dialog.setOnCancelListener(dialog1 -> StartConversationActivity.this.finish());
891 dialog.show();
892 }
893
894 protected void filter(String needle) {
895 if (xmppConnectionServiceBound) {
896 this.filterContacts(needle);
897 this.filterConferences(needle);
898 }
899 }
900
901 protected void filterContacts(String needle) {
902 this.contacts.clear();
903 for (Account account : xmppConnectionService.getAccounts()) {
904 if (account.getStatus() != Account.State.DISABLED) {
905 for (Contact contact : account.getRoster().getContacts()) {
906 Presence.Status s = contact.getShownStatus();
907 if (contact.showInRoster() && contact.match(this, needle)
908 && (!this.mHideOfflineContacts
909 || (needle != null && !needle.trim().isEmpty())
910 || s.compareTo(Presence.Status.OFFLINE) < 0)) {
911 this.contacts.add(contact);
912 }
913 }
914 }
915 }
916 Collections.sort(this.contacts);
917 mContactsAdapter.notifyDataSetChanged();
918 }
919
920 protected void filterConferences(String needle) {
921 this.conferences.clear();
922 for (Account account : xmppConnectionService.getAccounts()) {
923 if (account.getStatus() != Account.State.DISABLED) {
924 for (Bookmark bookmark : account.getBookmarks()) {
925 if (bookmark.match(this, needle)) {
926 this.conferences.add(bookmark);
927 }
928 }
929 }
930 }
931 Collections.sort(this.conferences);
932 mConferenceAdapter.notifyDataSetChanged();
933 }
934
935 private void onTabChanged() {
936 invalidateOptionsMenu();
937 }
938
939 @Override
940 public void OnUpdateBlocklist(final Status status) {
941 refreshUi();
942 }
943
944 @Override
945 protected void refreshUiReal() {
946 if (mSearchEditText != null) {
947 filter(mSearchEditText.getText().toString());
948 }
949 }
950
951 public class ListPagerAdapter extends PagerAdapter {
952 FragmentManager fragmentManager;
953 MyListFragment[] fragments;
954
955 public ListPagerAdapter(FragmentManager fm) {
956 fragmentManager = fm;
957 fragments = new MyListFragment[2];
958 }
959
960 public void requestFocus(int pos) {
961 if (fragments.length > pos) {
962 fragments[pos].getListView().requestFocus();
963 }
964 }
965
966 @Override
967 public void destroyItem(ViewGroup container, int position, Object object) {
968 assert (0 <= position && position < fragments.length);
969 FragmentTransaction trans = fragmentManager.beginTransaction();
970 trans.remove(fragments[position]);
971 trans.commit();
972 fragments[position] = null;
973 }
974
975 @Override
976 public Fragment instantiateItem(ViewGroup container, int position) {
977 Fragment fragment = getItem(position);
978 FragmentTransaction trans = fragmentManager.beginTransaction();
979 trans.add(container.getId(), fragment, "fragment:" + position);
980 trans.commit();
981 return fragment;
982 }
983
984 @Override
985 public int getCount() {
986 return fragments.length;
987 }
988
989 @Override
990 public boolean isViewFromObject(View view, Object fragment) {
991 return ((Fragment) fragment).getView() == view;
992 }
993
994 public Fragment getItem(int position) {
995 assert (0 <= position && position < fragments.length);
996 if (fragments[position] == null) {
997 final MyListFragment listFragment = new MyListFragment();
998 if (position == 1) {
999 listFragment.setListAdapter(mConferenceAdapter);
1000 listFragment.setContextMenu(R.menu.conference_context);
1001 listFragment.setOnListItemClickListener(new OnItemClickListener() {
1002
1003 @Override
1004 public void onItemClick(AdapterView<?> arg0, View arg1,
1005 int position, long arg3) {
1006 openConversationForBookmark(position);
1007 }
1008 });
1009 } else {
1010
1011 listFragment.setListAdapter(mContactsAdapter);
1012 listFragment.setContextMenu(R.menu.contact_context);
1013 listFragment.setOnListItemClickListener(new OnItemClickListener() {
1014
1015 @Override
1016 public void onItemClick(AdapterView<?> arg0, View arg1,
1017 int position, long arg3) {
1018 openConversationForContact(position);
1019 }
1020 });
1021 }
1022 fragments[position] = listFragment;
1023 }
1024 return fragments[position];
1025 }
1026 }
1027
1028 public static class MyListFragment extends ListFragment {
1029 private AdapterView.OnItemClickListener mOnItemClickListener;
1030 private int mResContextMenu;
1031
1032 public void setContextMenu(final int res) {
1033 this.mResContextMenu = res;
1034 }
1035
1036 @Override
1037 public void onListItemClick(final ListView l, final View v, final int position, final long id) {
1038 if (mOnItemClickListener != null) {
1039 mOnItemClickListener.onItemClick(l, v, position, id);
1040 }
1041 }
1042
1043 public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
1044 this.mOnItemClickListener = l;
1045 }
1046
1047 @Override
1048 public void onViewCreated(final View view, final Bundle savedInstanceState) {
1049 super.onViewCreated(view, savedInstanceState);
1050 registerForContextMenu(getListView());
1051 getListView().setFastScrollEnabled(true);
1052 }
1053
1054 @Override
1055 public void onCreateContextMenu(final ContextMenu menu, final View v,
1056 final ContextMenuInfo menuInfo) {
1057 super.onCreateContextMenu(menu, v, menuInfo);
1058 final StartConversationActivity activity = (StartConversationActivity) getActivity();
1059 activity.getMenuInflater().inflate(mResContextMenu, menu);
1060 final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
1061 if (mResContextMenu == R.menu.conference_context) {
1062 activity.conference_context_id = acmi.position;
1063 } else if (mResContextMenu == R.menu.contact_context) {
1064 activity.contact_context_id = acmi.position;
1065 final Contact contact = (Contact) activity.contacts.get(acmi.position);
1066 final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
1067 final MenuItem showContactDetailsItem = menu.findItem(R.id.context_contact_details);
1068 if (contact.isSelf()) {
1069 showContactDetailsItem.setVisible(false);
1070 }
1071 XmppConnection xmpp = contact.getAccount().getXmppConnection();
1072 if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) {
1073 if (contact.isBlocked()) {
1074 blockUnblockItem.setTitle(R.string.unblock_contact);
1075 } else {
1076 blockUnblockItem.setTitle(R.string.block_contact);
1077 }
1078 } else {
1079 blockUnblockItem.setVisible(false);
1080 }
1081 }
1082 }
1083
1084 @Override
1085 public boolean onContextItemSelected(final MenuItem item) {
1086 StartConversationActivity activity = (StartConversationActivity) getActivity();
1087 switch (item.getItemId()) {
1088 case R.id.context_start_conversation:
1089 activity.openConversationForContact();
1090 break;
1091 case R.id.context_contact_details:
1092 activity.openDetailsForContact();
1093 break;
1094 case R.id.context_contact_block_unblock:
1095 activity.toggleContactBlock();
1096 break;
1097 case R.id.context_delete_contact:
1098 activity.deleteContact();
1099 break;
1100 case R.id.context_join_conference:
1101 activity.openConversationForBookmark();
1102 break;
1103 case R.id.context_share_uri:
1104 activity.shareBookmarkUri();
1105 break;
1106 case R.id.context_delete_conference:
1107 activity.deleteConference();
1108 }
1109 return true;
1110 }
1111 }
1112
1113 private class Invite extends XmppUri {
1114
1115 public Invite(final Uri uri) {
1116 super(uri);
1117 }
1118
1119 public Invite(final String uri) {
1120 super(uri);
1121 }
1122
1123 public Invite(Uri uri, boolean safeSource) {
1124 super(uri,safeSource);
1125 }
1126
1127 public String account;
1128
1129 boolean invite() {
1130 if (!isJidValid()) {
1131 Toast.makeText(StartConversationActivity.this,R.string.invalid_jid,Toast.LENGTH_SHORT).show();
1132 return false;
1133 }
1134 if (getJid() != null) {
1135 return handleJid(this);
1136 }
1137 return false;
1138 }
1139 }
1140}