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