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