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 intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID,R.string.choose_participants);
560 startActivityForResult(intent, REQUEST_CREATE_CONFERENCE);
561 }
562 });
563 builder.setNegativeButton(R.string.cancel, null);
564 builder.create().show();
565 }
566
567 private Account getSelectedAccount(Spinner spinner) {
568 if (!spinner.isEnabled()) {
569 return null;
570 }
571 Jid jid;
572 try {
573 if (Config.DOMAIN_LOCK != null) {
574 jid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
575 } else {
576 jid = Jid.fromString((String) spinner.getSelectedItem());
577 }
578 } catch (final InvalidJidException e) {
579 return null;
580 }
581 return xmppConnectionService.findAccountByJid(jid);
582 }
583
584 protected void switchToConversation(Contact contact) {
585 Conversation conversation = xmppConnectionService
586 .findOrCreateConversation(contact.getAccount(),
587 contact.getJid(), false);
588 switchToConversation(conversation);
589 }
590
591 public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) {
592 if (accounts.size() > 0) {
593 ArrayAdapter<String> adapter = new ArrayAdapter<>(context, R.layout.simple_list_item, accounts);
594 adapter.setDropDownViewResource(R.layout.simple_list_item);
595 spinner.setAdapter(adapter);
596 spinner.setEnabled(true);
597 } else {
598 ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
599 R.layout.simple_list_item,
600 Arrays.asList(new String[]{context.getString(R.string.no_accounts)}));
601 adapter.setDropDownViewResource(R.layout.simple_list_item);
602 spinner.setAdapter(adapter);
603 spinner.setEnabled(false);
604 }
605 }
606
607 @Override
608 public boolean onCreateOptionsMenu(Menu menu) {
609 getMenuInflater().inflate(R.menu.start_conversation, menu);
610 MenuItem menuCreateContact = menu.findItem(R.id.action_create_contact);
611 MenuItem menuCreateConference = menu.findItem(R.id.action_conference);
612 MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline);
613 menuHideOffline.setChecked(this.mHideOfflineContacts);
614 mMenuSearchView = menu.findItem(R.id.action_search);
615 mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
616 View mSearchView = mMenuSearchView.getActionView();
617 mSearchEditText = (EditText) mSearchView
618 .findViewById(R.id.search_field);
619 mSearchEditText.addTextChangedListener(mSearchTextWatcher);
620 mSearchEditText.setOnEditorActionListener(mSearchDone);
621 if (getActionBar().getSelectedNavigationIndex() == 0) {
622 menuCreateConference.setVisible(false);
623 } else {
624 menuCreateContact.setVisible(false);
625 }
626 if (mInitialJid != null) {
627 mMenuSearchView.expandActionView();
628 mSearchEditText.append(mInitialJid);
629 filter(mInitialJid);
630 }
631 return super.onCreateOptionsMenu(menu);
632 }
633
634 @Override
635 public boolean onOptionsItemSelected(MenuItem item) {
636 switch (item.getItemId()) {
637 case R.id.action_create_contact:
638 showCreateContactDialog(null,null);
639 return true;
640 case R.id.action_join_conference:
641 showJoinConferenceDialog(null);
642 return true;
643 case R.id.action_create_conference:
644 showCreateConferenceDialog();
645 return true;
646 case R.id.action_scan_qr_code:
647 new IntentIntegrator(this).initiateScan();
648 return true;
649 case R.id.action_hide_offline:
650 mHideOfflineContacts = !item.isChecked();
651 getPreferences().edit().putBoolean("hide_offline", mHideOfflineContacts).commit();
652 if (mSearchEditText != null) {
653 filter(mSearchEditText.getText().toString());
654 }
655 invalidateOptionsMenu();
656 }
657 return super.onOptionsItemSelected(item);
658 }
659
660 @Override
661 public boolean onKeyUp(int keyCode, KeyEvent event) {
662 if (keyCode == KeyEvent.KEYCODE_SEARCH && !event.isLongPress()) {
663 openSearch();
664 return true;
665 }
666 int c = event.getUnicodeChar();
667 if (c > 32) {
668 if (mSearchEditText != null && !mSearchEditText.isFocused()) {
669 openSearch();
670 mSearchEditText.append(Character.toString((char) c));
671 return true;
672 }
673 }
674 return super.onKeyUp(keyCode, event);
675 }
676
677 private void openSearch() {
678 if (mMenuSearchView != null) {
679 mMenuSearchView.expandActionView();
680 }
681 }
682
683 @Override
684 public void onActivityResult(int requestCode, int resultCode, Intent intent) {
685 if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) {
686 IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
687 if (scanResult != null && scanResult.getFormatName() != null) {
688 String data = scanResult.getContents();
689 Invite invite = new Invite(data);
690 if (xmppConnectionServiceBound) {
691 invite.invite();
692 } else if (invite.getJid() != null) {
693 this.mPendingInvite = invite;
694 } else {
695 this.mPendingInvite = null;
696 }
697 }
698 } else if (resultCode == RESULT_OK) {
699 if (xmppConnectionServiceBound) {
700 this.mPostponedActivityResult = null;
701 if (requestCode == REQUEST_CREATE_CONFERENCE) {
702 Log.d(Config.LOGTAG,"account jid: "+ intent.getStringExtra(EXTRA_ACCOUNT));
703 Account account = extractAccount(intent);
704 final String subject = intent.getStringExtra("subject");
705 List<Jid> jids = new ArrayList<>();
706 if (intent.getBooleanExtra("multiple", false)) {
707 String[] toAdd = intent.getStringArrayExtra("contacts");
708 for (String item : toAdd) {
709 try {
710 jids.add(Jid.fromString(item));
711 } catch (InvalidJidException e) {
712 //ignored
713 }
714 }
715 } else {
716 try {
717 jids.add(Jid.fromString(intent.getStringExtra("contact")));
718 } catch (Exception e) {
719 //ignored
720 }
721 }
722 if (account != null && jids.size() > 1) {
723 xmppConnectionService.createAdhocConference(account, subject, jids, mAdhocConferenceCallback);
724 mToast = Toast.makeText(this, R.string.creating_conference,Toast.LENGTH_LONG);
725 mToast.show();
726 }
727 }
728 } else {
729 this.mPostponedActivityResult = new Pair<>(requestCode, intent);
730 }
731 }
732 super.onActivityResult(requestCode, requestCode, intent);
733 }
734
735 private void askForContactsPermissions() {
736 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
737 if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
738 if (mRequestedContactsPermission.compareAndSet(false, true)) {
739 if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
740 AlertDialog.Builder builder = new AlertDialog.Builder(this);
741 builder.setTitle(R.string.sync_with_contacts);
742 builder.setMessage(R.string.sync_with_contacts_long);
743 builder.setPositiveButton(R.string.next, new OnClickListener() {
744 @Override
745 public void onClick(DialogInterface dialog, int which) {
746 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
747 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
748 }
749 }
750 });
751 builder.create().show();
752 } else {
753 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 0);
754 }
755 }
756 }
757 }
758 }
759
760 @Override
761 public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
762 if (grantResults.length > 0)
763 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
764 if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
765 xmppConnectionService.loadPhoneContacts();
766 }
767 }
768 }
769
770 @Override
771 protected void onBackendConnected() {
772 if (mPostponedActivityResult != null) {
773 onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
774 this.mPostponedActivityResult = null;
775 }
776 this.mActivatedAccounts.clear();
777 for (Account account : xmppConnectionService.getAccounts()) {
778 if (account.getStatus() != Account.State.DISABLED) {
779 if (Config.DOMAIN_LOCK != null) {
780 this.mActivatedAccounts.add(account.getJid().getLocalpart());
781 } else {
782 this.mActivatedAccounts.add(account.getJid().toBareJid().toString());
783 }
784 }
785 }
786 final Intent intent = getIntent();
787 final ActionBar ab = getActionBar();
788 if (intent != null && intent.getBooleanExtra("init",false) && ab != null) {
789 ab.setDisplayShowHomeEnabled(false);
790 ab.setDisplayHomeAsUpEnabled(false);
791 ab.setHomeButtonEnabled(false);
792 }
793 this.mKnownHosts = xmppConnectionService.getKnownHosts();
794 this.mKnownConferenceHosts = xmppConnectionService.getKnownConferenceHosts();
795 if (this.mPendingInvite != null) {
796 mPendingInvite.invite();
797 this.mPendingInvite = null;
798 } else if (!handleIntent(getIntent())) {
799 if (mSearchEditText != null) {
800 filter(mSearchEditText.getText().toString());
801 } else {
802 filter(null);
803 }
804 }
805 setIntent(null);
806 }
807
808 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
809 Invite getInviteJellyBean(NdefRecord record) {
810 return new Invite(record.toUri());
811 }
812
813 protected boolean handleIntent(Intent intent) {
814 if (intent == null || intent.getAction() == null) {
815 return false;
816 }
817 switch (intent.getAction()) {
818 case Intent.ACTION_SENDTO:
819 case Intent.ACTION_VIEW:
820 Log.d(Config.LOGTAG, "received uri=" + intent.getData());
821 return new Invite(intent.getData()).invite();
822 case NfcAdapter.ACTION_NDEF_DISCOVERED:
823 for (Parcelable message : getIntent().getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
824 if (message instanceof NdefMessage) {
825 Log.d(Config.LOGTAG, "received message=" + message);
826 for (NdefRecord record : ((NdefMessage) message).getRecords()) {
827 switch (record.getTnf()) {
828 case NdefRecord.TNF_WELL_KNOWN:
829 if (Arrays.equals(record.getType(), NdefRecord.RTD_URI)) {
830 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
831 return getInviteJellyBean(record).invite();
832 } else {
833 byte[] payload = record.getPayload();
834 if (payload[0] == 0) {
835 return new Invite(Uri.parse(new String(Arrays.copyOfRange(
836 payload, 1, payload.length)))).invite();
837 }
838 }
839 }
840 }
841 }
842 }
843 }
844 }
845 return false;
846 }
847
848 private boolean handleJid(Invite invite) {
849 List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid());
850 if (contacts.size() == 0) {
851 showCreateContactDialog(invite.getJid().toString(),invite.getFingerprint());
852 return false;
853 } else if (contacts.size() == 1) {
854 Contact contact = contacts.get(0);
855 if (invite.getFingerprint() != null) {
856 if (contact.addOtrFingerprint(invite.getFingerprint())) {
857 Log.d(Config.LOGTAG,"added new fingerprint");
858 xmppConnectionService.syncRosterToDisk(contact.getAccount());
859 }
860 }
861 switchToConversation(contact);
862 return true;
863 } else {
864 if (mMenuSearchView != null) {
865 mMenuSearchView.expandActionView();
866 mSearchEditText.setText("");
867 mSearchEditText.append(invite.getJid().toString());
868 filter(invite.getJid().toString());
869 } else {
870 mInitialJid = invite.getJid().toString();
871 }
872 return true;
873 }
874 }
875
876 protected void filter(String needle) {
877 if (xmppConnectionServiceBound) {
878 this.filterContacts(needle);
879 this.filterConferences(needle);
880 }
881 }
882
883 protected void filterContacts(String needle) {
884 this.contacts.clear();
885 for (Account account : xmppConnectionService.getAccounts()) {
886 if (account.getStatus() != Account.State.DISABLED) {
887 for (Contact contact : account.getRoster().getContacts()) {
888 Presence p = contact.getPresences().getMostAvailablePresence();
889 Presence.Status s = p == null ? Presence.Status.OFFLINE : p.getStatus();
890 if (contact.showInRoster() && contact.match(this, needle)
891 && (!this.mHideOfflineContacts
892 || (needle != null && !needle.trim().isEmpty())
893 || s.compareTo(Presence.Status.OFFLINE) < 0)) {
894 this.contacts.add(contact);
895 }
896 }
897 }
898 }
899 Collections.sort(this.contacts);
900 mContactsAdapter.notifyDataSetChanged();
901 }
902
903 protected void filterConferences(String needle) {
904 this.conferences.clear();
905 for (Account account : xmppConnectionService.getAccounts()) {
906 if (account.getStatus() != Account.State.DISABLED) {
907 for (Bookmark bookmark : account.getBookmarks()) {
908 if (bookmark.match(this, needle)) {
909 this.conferences.add(bookmark);
910 }
911 }
912 }
913 }
914 Collections.sort(this.conferences);
915 mConferenceAdapter.notifyDataSetChanged();
916 }
917
918 private void onTabChanged() {
919 invalidateOptionsMenu();
920 }
921
922 @Override
923 public void OnUpdateBlocklist(final Status status) {
924 refreshUi();
925 }
926
927 @Override
928 protected void refreshUiReal() {
929 if (mSearchEditText != null) {
930 filter(mSearchEditText.getText().toString());
931 }
932 }
933
934 public static class MyListFragment extends ListFragment {
935 private AdapterView.OnItemClickListener mOnItemClickListener;
936 private int mResContextMenu;
937
938 public void setContextMenu(final int res) {
939 this.mResContextMenu = res;
940 }
941
942 @Override
943 public void onListItemClick(final ListView l, final View v, final int position, final long id) {
944 if (mOnItemClickListener != null) {
945 mOnItemClickListener.onItemClick(l, v, position, id);
946 }
947 }
948
949 public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
950 this.mOnItemClickListener = l;
951 }
952
953 @Override
954 public void onViewCreated(final View view, final Bundle savedInstanceState) {
955 super.onViewCreated(view, savedInstanceState);
956 registerForContextMenu(getListView());
957 getListView().setFastScrollEnabled(true);
958 }
959
960 @Override
961 public void onCreateContextMenu(final ContextMenu menu, final View v,
962 final ContextMenuInfo menuInfo) {
963 super.onCreateContextMenu(menu, v, menuInfo);
964 final StartConversationActivity activity = (StartConversationActivity) getActivity();
965 activity.getMenuInflater().inflate(mResContextMenu, menu);
966 final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
967 if (mResContextMenu == R.menu.conference_context) {
968 activity.conference_context_id = acmi.position;
969 } else if (mResContextMenu == R.menu.contact_context){
970 activity.contact_context_id = acmi.position;
971 final Blockable contact = (Contact) activity.contacts.get(acmi.position);
972 final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
973 XmppConnection xmpp = contact.getAccount().getXmppConnection();
974 if (xmpp != null && xmpp.getFeatures().blocking()) {
975 if (contact.isBlocked()) {
976 blockUnblockItem.setTitle(R.string.unblock_contact);
977 } else {
978 blockUnblockItem.setTitle(R.string.block_contact);
979 }
980 } else {
981 blockUnblockItem.setVisible(false);
982 }
983 }
984 }
985
986 @Override
987 public boolean onContextItemSelected(final MenuItem item) {
988 StartConversationActivity activity = (StartConversationActivity) getActivity();
989 switch (item.getItemId()) {
990 case R.id.context_start_conversation:
991 activity.openConversationForContact();
992 break;
993 case R.id.context_contact_details:
994 activity.openDetailsForContact();
995 break;
996 case R.id.context_contact_block_unblock:
997 activity.toggleContactBlock();
998 break;
999 case R.id.context_delete_contact:
1000 activity.deleteContact();
1001 break;
1002 case R.id.context_join_conference:
1003 activity.openConversationForBookmark();
1004 break;
1005 case R.id.context_delete_conference:
1006 activity.deleteConference();
1007 }
1008 return true;
1009 }
1010 }
1011
1012 private class Invite extends XmppUri {
1013
1014 public Invite(final Uri uri) {
1015 super(uri);
1016 }
1017
1018 public Invite(final String uri) {
1019 super(uri);
1020 }
1021
1022 boolean invite() {
1023 if (jid != null) {
1024 if (muc) {
1025 showJoinConferenceDialog(jid);
1026 } else {
1027 return handleJid(this);
1028 }
1029 }
1030 return false;
1031 }
1032 }
1033}