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