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 String fingerprint) {
401 EnterJidDialog dialog = new EnterJidDialog(
402 this, mKnownHosts, mActivatedAccounts,
403 getString(R.string.create_contact), getString(R.string.create),
404 prefilledJid, null, fingerprint == null
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 contact.addOtrFingerprint(fingerprint);
424 xmppConnectionService.createContact(contact);
425 switchToConversation(contact);
426 return true;
427 }
428 }
429 });
430
431 mCurrentDialog = dialog.show();
432 }
433
434 @SuppressLint("InflateParams")
435 protected void showJoinConferenceDialog(final String prefilledJid) {
436 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
437 builder.setTitle(R.string.join_conference);
438 final View dialogView = getLayoutInflater().inflate(R.layout.join_conference_dialog, null);
439 final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
440 final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid);
441 final TextView jabberIdDesc = (TextView) dialogView.findViewById(R.id.jabber_id);
442 jabberIdDesc.setText(R.string.conference_address);
443 jid.setHint(R.string.conference_address_example);
444 jid.setAdapter(new KnownHostsAdapter(this, R.layout.simple_list_item, mKnownConferenceHosts));
445 if (prefilledJid != null) {
446 jid.append(prefilledJid);
447 }
448 populateAccountSpinner(this, mActivatedAccounts, spinner);
449 final Checkable bookmarkCheckBox = (CheckBox) dialogView
450 .findViewById(R.id.bookmark);
451 builder.setView(dialogView);
452 builder.setNegativeButton(R.string.cancel, null);
453 builder.setPositiveButton(R.string.join, null);
454 final AlertDialog dialog = builder.create();
455 dialog.show();
456 mCurrentDialog = dialog;
457 dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
458 new View.OnClickListener() {
459
460 @Override
461 public void onClick(final View v) {
462 if (!xmppConnectionServiceBound) {
463 return;
464 }
465 final Account account = getSelectedAccount(spinner);
466 if (account == null) {
467 return;
468 }
469 final Jid conferenceJid;
470 try {
471 conferenceJid = Jid.fromString(jid.getText().toString());
472 } catch (final InvalidJidException e) {
473 jid.setError(getString(R.string.invalid_jid));
474 return;
475 }
476
477 if (bookmarkCheckBox.isChecked()) {
478 if (account.hasBookmarkFor(conferenceJid)) {
479 jid.setError(getString(R.string.bookmark_already_exists));
480 } else {
481 final Bookmark bookmark = new Bookmark(account, conferenceJid.toBareJid());
482 bookmark.setAutojoin(getPreferences().getBoolean("autojoin", true));
483 String nick = conferenceJid.getResourcepart();
484 if (nick != null && !nick.isEmpty()) {
485 bookmark.setNick(nick);
486 }
487 account.getBookmarks().add(bookmark);
488 xmppConnectionService.pushBookmarks(account);
489 final Conversation conversation = xmppConnectionService
490 .findOrCreateConversation(account,
491 conferenceJid, true);
492 conversation.setBookmark(bookmark);
493 if (!conversation.getMucOptions().online()) {
494 xmppConnectionService.joinMuc(conversation);
495 }
496 dialog.dismiss();
497 mCurrentDialog = null;
498 switchToConversation(conversation);
499 }
500 } else {
501 final Conversation conversation = xmppConnectionService
502 .findOrCreateConversation(account,
503 conferenceJid, true);
504 if (!conversation.getMucOptions().online()) {
505 xmppConnectionService.joinMuc(conversation);
506 }
507 dialog.dismiss();
508 mCurrentDialog = null;
509 switchToConversation(conversation);
510 }
511 }
512 });
513 }
514
515 private void showCreateConferenceDialog() {
516 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
517 builder.setTitle(R.string.create_conference);
518 final View dialogView = getLayoutInflater().inflate(R.layout.create_conference_dialog, null);
519 final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
520 final EditText subject = (EditText) dialogView.findViewById(R.id.subject);
521 populateAccountSpinner(this, mActivatedAccounts, spinner);
522 builder.setView(dialogView);
523 builder.setPositiveButton(R.string.choose_participants, new OnClickListener() {
524 @Override
525 public void onClick(DialogInterface dialog, int which) {
526 if (!xmppConnectionServiceBound) {
527 return;
528 }
529 final Account account = getSelectedAccount(spinner);
530 if (account == null) {
531 return;
532 }
533 Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class);
534 intent.putExtra("multiple", true);
535 intent.putExtra("show_enter_jid", true);
536 intent.putExtra("subject", subject.getText().toString());
537 intent.putExtra(EXTRA_ACCOUNT, account.getJid().toBareJid().toString());
538 intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants);
539 startActivityForResult(intent, REQUEST_CREATE_CONFERENCE);
540 }
541 });
542 builder.setNegativeButton(R.string.cancel, null);
543 mCurrentDialog = builder.create();
544 mCurrentDialog.show();
545 }
546
547 private Account getSelectedAccount(Spinner spinner) {
548 if (!spinner.isEnabled()) {
549 return null;
550 }
551 Jid jid;
552 try {
553 if (Config.DOMAIN_LOCK != null) {
554 jid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
555 } else {
556 jid = Jid.fromString((String) spinner.getSelectedItem());
557 }
558 } catch (final InvalidJidException e) {
559 return null;
560 }
561 return xmppConnectionService.findAccountByJid(jid);
562 }
563
564 protected void switchToConversation(Contact contact) {
565 Conversation conversation = xmppConnectionService
566 .findOrCreateConversation(contact.getAccount(),
567 contact.getJid(), false);
568 switchToConversation(conversation);
569 }
570
571 public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) {
572 if (accounts.size() > 0) {
573 ArrayAdapter<String> adapter = new ArrayAdapter<>(context, R.layout.simple_list_item, accounts);
574 adapter.setDropDownViewResource(R.layout.simple_list_item);
575 spinner.setAdapter(adapter);
576 spinner.setEnabled(true);
577 } else {
578 ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
579 R.layout.simple_list_item,
580 Arrays.asList(new String[]{context.getString(R.string.no_accounts)}));
581 adapter.setDropDownViewResource(R.layout.simple_list_item);
582 spinner.setAdapter(adapter);
583 spinner.setEnabled(false);
584 }
585 }
586
587 @Override
588 public boolean onCreateOptionsMenu(Menu menu) {
589 getMenuInflater().inflate(R.menu.start_conversation, menu);
590 MenuItem menuCreateContact = menu.findItem(R.id.action_create_contact);
591 MenuItem menuCreateConference = menu.findItem(R.id.action_conference);
592 MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline);
593 menuHideOffline.setChecked(this.mHideOfflineContacts);
594 mMenuSearchView = menu.findItem(R.id.action_search);
595 mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
596 View mSearchView = mMenuSearchView.getActionView();
597 mSearchEditText = (EditText) mSearchView
598 .findViewById(R.id.search_field);
599 mSearchEditText.addTextChangedListener(mSearchTextWatcher);
600 mSearchEditText.setOnEditorActionListener(mSearchDone);
601 if (getActionBar().getSelectedNavigationIndex() == 0) {
602 menuCreateConference.setVisible(false);
603 } else {
604 menuCreateContact.setVisible(false);
605 }
606 if (mInitialJid != null) {
607 mMenuSearchView.expandActionView();
608 mSearchEditText.append(mInitialJid);
609 filter(mInitialJid);
610 }
611 return super.onCreateOptionsMenu(menu);
612 }
613
614 @Override
615 public boolean onOptionsItemSelected(MenuItem item) {
616 switch (item.getItemId()) {
617 case R.id.action_create_contact:
618 showCreateContactDialog(null, null);
619 return true;
620 case R.id.action_join_conference:
621 showJoinConferenceDialog(null);
622 return true;
623 case R.id.action_create_conference:
624 showCreateConferenceDialog();
625 return true;
626 case R.id.action_scan_qr_code:
627 new IntentIntegrator(this).initiateScan();
628 return true;
629 case R.id.action_hide_offline:
630 mHideOfflineContacts = !item.isChecked();
631 getPreferences().edit().putBoolean("hide_offline", mHideOfflineContacts).commit();
632 if (mSearchEditText != null) {
633 filter(mSearchEditText.getText().toString());
634 }
635 invalidateOptionsMenu();
636 }
637 return super.onOptionsItemSelected(item);
638 }
639
640 @Override
641 public boolean onKeyUp(int keyCode, KeyEvent event) {
642 if (keyCode == KeyEvent.KEYCODE_SEARCH && !event.isLongPress()) {
643 openSearch();
644 return true;
645 }
646 int c = event.getUnicodeChar();
647 if (c > 32) {
648 if (mSearchEditText != null && !mSearchEditText.isFocused()) {
649 openSearch();
650 mSearchEditText.append(Character.toString((char) c));
651 return true;
652 }
653 }
654 return super.onKeyUp(keyCode, event);
655 }
656
657 private void openSearch() {
658 if (mMenuSearchView != null) {
659 mMenuSearchView.expandActionView();
660 }
661 }
662
663 @Override
664 public void onActivityResult(int requestCode, int resultCode, Intent intent) {
665 if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) {
666 IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
667 if (scanResult != null && scanResult.getFormatName() != null) {
668 String data = scanResult.getContents();
669 Invite invite = new Invite(data);
670 if (xmppConnectionServiceBound) {
671 invite.invite();
672 } else if (invite.getJid() != null) {
673 this.mPendingInvite = invite;
674 } else {
675 this.mPendingInvite = null;
676 }
677 }
678 } else if (resultCode == RESULT_OK) {
679 if (xmppConnectionServiceBound) {
680 this.mPostponedActivityResult = null;
681 if (requestCode == REQUEST_CREATE_CONFERENCE) {
682 Account account = extractAccount(intent);
683 final String subject = intent.getStringExtra("subject");
684 List<Jid> jids = new ArrayList<>();
685 if (intent.getBooleanExtra("multiple", false)) {
686 String[] toAdd = intent.getStringArrayExtra("contacts");
687 for (String item : toAdd) {
688 try {
689 jids.add(Jid.fromString(item));
690 } catch (InvalidJidException e) {
691 //ignored
692 }
693 }
694 } else {
695 try {
696 jids.add(Jid.fromString(intent.getStringExtra("contact")));
697 } catch (Exception e) {
698 //ignored
699 }
700 }
701 if (account != null && jids.size() > 0) {
702 xmppConnectionService.createAdhocConference(account, subject, jids, mAdhocConferenceCallback);
703 mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
704 mToast.show();
705 }
706 }
707 } else {
708 this.mPostponedActivityResult = new Pair<>(requestCode, intent);
709 }
710 }
711 super.onActivityResult(requestCode, requestCode, intent);
712 }
713
714 private void askForContactsPermissions() {
715 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
716 if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
717 if (mRequestedContactsPermission.compareAndSet(false, true)) {
718 if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
719 AlertDialog.Builder builder = new AlertDialog.Builder(this);
720 builder.setTitle(R.string.sync_with_contacts);
721 builder.setMessage(R.string.sync_with_contacts_long);
722 builder.setPositiveButton(R.string.next, new OnClickListener() {
723 @Override
724 public void onClick(DialogInterface dialog, int which) {
725 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
726 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
727 }
728 }
729 });
730 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
731 builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
732 @Override
733 public void onDismiss(DialogInterface dialog) {
734 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
735 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
736 }
737 }
738 });
739 }
740 builder.create().show();
741 } else {
742 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 0);
743 }
744 }
745 }
746 }
747 }
748
749 @Override
750 public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
751 if (grantResults.length > 0)
752 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
753 if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
754 xmppConnectionService.loadPhoneContacts();
755 }
756 }
757 }
758
759 @Override
760 protected void onBackendConnected() {
761 if (mPostponedActivityResult != null) {
762 onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
763 this.mPostponedActivityResult = null;
764 }
765 this.mActivatedAccounts.clear();
766 for (Account account : xmppConnectionService.getAccounts()) {
767 if (account.getStatus() != Account.State.DISABLED) {
768 if (Config.DOMAIN_LOCK != null) {
769 this.mActivatedAccounts.add(account.getJid().getLocalpart());
770 } else {
771 this.mActivatedAccounts.add(account.getJid().toBareJid().toString());
772 }
773 }
774 }
775 final Intent intent = getIntent();
776 final ActionBar ab = getActionBar();
777 boolean init = intent != null && intent.getBooleanExtra("init", false);
778 boolean noConversations = xmppConnectionService.getConversations().size() == 0;
779 if ((init || noConversations) && ab != null) {
780 ab.setDisplayShowHomeEnabled(false);
781 ab.setDisplayHomeAsUpEnabled(false);
782 ab.setHomeButtonEnabled(false);
783 }
784 this.mKnownHosts = xmppConnectionService.getKnownHosts();
785 this.mKnownConferenceHosts = xmppConnectionService.getKnownConferenceHosts();
786 if (this.mPendingInvite != null) {
787 mPendingInvite.invite();
788 this.mPendingInvite = null;
789 } else if (!handleIntent(getIntent())) {
790 if (mSearchEditText != null) {
791 filter(mSearchEditText.getText().toString());
792 } else {
793 filter(null);
794 }
795 }
796 setIntent(null);
797 }
798
799 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
800 Invite getInviteJellyBean(NdefRecord record) {
801 return new Invite(record.toUri());
802 }
803
804 protected boolean handleIntent(Intent intent) {
805 if (intent == null || intent.getAction() == null) {
806 return false;
807 }
808 switch (intent.getAction()) {
809 case Intent.ACTION_SENDTO:
810 case Intent.ACTION_VIEW:
811 Uri uri = intent.getData();
812 if (uri != null) {
813 Log.d(Config.LOGTAG, "received uri=" + intent.getData());
814 return new Invite(intent.getData()).invite();
815 } else {
816 return false;
817 }
818 case NfcAdapter.ACTION_NDEF_DISCOVERED:
819 for (Parcelable message : getIntent().getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
820 if (message instanceof NdefMessage) {
821 Log.d(Config.LOGTAG, "received message=" + message);
822 for (NdefRecord record : ((NdefMessage) message).getRecords()) {
823 switch (record.getTnf()) {
824 case NdefRecord.TNF_WELL_KNOWN:
825 if (Arrays.equals(record.getType(), NdefRecord.RTD_URI)) {
826 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
827 return getInviteJellyBean(record).invite();
828 } else {
829 byte[] payload = record.getPayload();
830 if (payload[0] == 0) {
831 return new Invite(Uri.parse(new String(Arrays.copyOfRange(
832 payload, 1, payload.length)))).invite();
833 }
834 }
835 }
836 }
837 }
838 }
839 }
840 }
841 return false;
842 }
843
844 private boolean handleJid(Invite invite) {
845 List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid());
846 if (invite.isMuc()) {
847 Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
848 if (muc != null) {
849 switchToConversation(muc);
850 return true;
851 } else {
852 showJoinConferenceDialog(invite.getJid().toBareJid().toString());
853 return false;
854 }
855 } else if (contacts.size() == 0) {
856 showCreateContactDialog(invite.getJid().toString(), invite.getFingerprint());
857 return false;
858 } else if (contacts.size() == 1) {
859 Contact contact = contacts.get(0);
860 if (invite.getFingerprint() != null) {
861 if (contact.addOtrFingerprint(invite.getFingerprint())) {
862 Log.d(Config.LOGTAG, "added new fingerprint");
863 xmppConnectionService.syncRosterToDisk(contact.getAccount());
864 }
865 }
866 switchToConversation(contact);
867 return true;
868 } else {
869 if (mMenuSearchView != null) {
870 mMenuSearchView.expandActionView();
871 mSearchEditText.setText("");
872 mSearchEditText.append(invite.getJid().toString());
873 filter(invite.getJid().toString());
874 } else {
875 mInitialJid = invite.getJid().toString();
876 }
877 return true;
878 }
879 }
880
881 protected void filter(String needle) {
882 if (xmppConnectionServiceBound) {
883 this.filterContacts(needle);
884 this.filterConferences(needle);
885 }
886 }
887
888 protected void filterContacts(String needle) {
889 this.contacts.clear();
890 for (Account account : xmppConnectionService.getAccounts()) {
891 if (account.getStatus() != Account.State.DISABLED) {
892 for (Contact contact : account.getRoster().getContacts()) {
893 Presence.Status s = contact.getShownStatus();
894 if (contact.showInRoster() && contact.match(this, needle)
895 && (!this.mHideOfflineContacts
896 || (needle != null && !needle.trim().isEmpty())
897 || s.compareTo(Presence.Status.OFFLINE) < 0)) {
898 this.contacts.add(contact);
899 }
900 }
901 }
902 }
903 Collections.sort(this.contacts);
904 mContactsAdapter.notifyDataSetChanged();
905 }
906
907 protected void filterConferences(String needle) {
908 this.conferences.clear();
909 for (Account account : xmppConnectionService.getAccounts()) {
910 if (account.getStatus() != Account.State.DISABLED) {
911 for (Bookmark bookmark : account.getBookmarks()) {
912 if (bookmark.match(this, needle)) {
913 this.conferences.add(bookmark);
914 }
915 }
916 }
917 }
918 Collections.sort(this.conferences);
919 mConferenceAdapter.notifyDataSetChanged();
920 }
921
922 private void onTabChanged() {
923 invalidateOptionsMenu();
924 }
925
926 @Override
927 public void OnUpdateBlocklist(final Status status) {
928 refreshUi();
929 }
930
931 @Override
932 protected void refreshUiReal() {
933 if (mSearchEditText != null) {
934 filter(mSearchEditText.getText().toString());
935 }
936 }
937
938 public class ListPagerAdapter extends PagerAdapter {
939 FragmentManager fragmentManager;
940 MyListFragment[] fragments;
941
942 public ListPagerAdapter(FragmentManager fm) {
943 fragmentManager = fm;
944 fragments = new MyListFragment[2];
945 }
946
947 public void requestFocus(int pos) {
948 if (fragments.length > pos) {
949 fragments[pos].getListView().requestFocus();
950 }
951 }
952
953 @Override
954 public void destroyItem(ViewGroup container, int position, Object object) {
955 assert (0 <= position && position < fragments.length);
956 FragmentTransaction trans = fragmentManager.beginTransaction();
957 trans.remove(fragments[position]);
958 trans.commit();
959 fragments[position] = null;
960 }
961
962 @Override
963 public Fragment instantiateItem(ViewGroup container, int position) {
964 Fragment fragment = getItem(position);
965 FragmentTransaction trans = fragmentManager.beginTransaction();
966 trans.add(container.getId(), fragment, "fragment:" + position);
967 trans.commit();
968 return fragment;
969 }
970
971 @Override
972 public int getCount() {
973 return fragments.length;
974 }
975
976 @Override
977 public boolean isViewFromObject(View view, Object fragment) {
978 return ((Fragment) fragment).getView() == view;
979 }
980
981 public Fragment getItem(int position) {
982 assert (0 <= position && position < fragments.length);
983 if (fragments[position] == null) {
984 final MyListFragment listFragment = new MyListFragment();
985 if (position == 1) {
986 listFragment.setListAdapter(mConferenceAdapter);
987 listFragment.setContextMenu(R.menu.conference_context);
988 listFragment.setOnListItemClickListener(new OnItemClickListener() {
989
990 @Override
991 public void onItemClick(AdapterView<?> arg0, View arg1,
992 int position, long arg3) {
993 openConversationForBookmark(position);
994 }
995 });
996 } else {
997
998 listFragment.setListAdapter(mContactsAdapter);
999 listFragment.setContextMenu(R.menu.contact_context);
1000 listFragment.setOnListItemClickListener(new OnItemClickListener() {
1001
1002 @Override
1003 public void onItemClick(AdapterView<?> arg0, View arg1,
1004 int position, long arg3) {
1005 openConversationForContact(position);
1006 }
1007 });
1008 }
1009 fragments[position] = listFragment;
1010 }
1011 return fragments[position];
1012 }
1013 }
1014
1015 public static class MyListFragment extends ListFragment {
1016 private AdapterView.OnItemClickListener mOnItemClickListener;
1017 private int mResContextMenu;
1018
1019 public void setContextMenu(final int res) {
1020 this.mResContextMenu = res;
1021 }
1022
1023 @Override
1024 public void onListItemClick(final ListView l, final View v, final int position, final long id) {
1025 if (mOnItemClickListener != null) {
1026 mOnItemClickListener.onItemClick(l, v, position, id);
1027 }
1028 }
1029
1030 public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
1031 this.mOnItemClickListener = l;
1032 }
1033
1034 @Override
1035 public void onViewCreated(final View view, final Bundle savedInstanceState) {
1036 super.onViewCreated(view, savedInstanceState);
1037 registerForContextMenu(getListView());
1038 getListView().setFastScrollEnabled(true);
1039 }
1040
1041 @Override
1042 public void onCreateContextMenu(final ContextMenu menu, final View v,
1043 final ContextMenuInfo menuInfo) {
1044 super.onCreateContextMenu(menu, v, menuInfo);
1045 final StartConversationActivity activity = (StartConversationActivity) getActivity();
1046 activity.getMenuInflater().inflate(mResContextMenu, menu);
1047 final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
1048 if (mResContextMenu == R.menu.conference_context) {
1049 activity.conference_context_id = acmi.position;
1050 } else if (mResContextMenu == R.menu.contact_context) {
1051 activity.contact_context_id = acmi.position;
1052 final Blockable contact = (Contact) activity.contacts.get(acmi.position);
1053 final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
1054 XmppConnection xmpp = contact.getAccount().getXmppConnection();
1055 if (xmpp != null && xmpp.getFeatures().blocking()) {
1056 if (contact.isBlocked()) {
1057 blockUnblockItem.setTitle(R.string.unblock_contact);
1058 } else {
1059 blockUnblockItem.setTitle(R.string.block_contact);
1060 }
1061 } else {
1062 blockUnblockItem.setVisible(false);
1063 }
1064 }
1065 }
1066
1067 @Override
1068 public boolean onContextItemSelected(final MenuItem item) {
1069 StartConversationActivity activity = (StartConversationActivity) getActivity();
1070 switch (item.getItemId()) {
1071 case R.id.context_start_conversation:
1072 activity.openConversationForContact();
1073 break;
1074 case R.id.context_contact_details:
1075 activity.openDetailsForContact();
1076 break;
1077 case R.id.context_contact_block_unblock:
1078 activity.toggleContactBlock();
1079 break;
1080 case R.id.context_delete_contact:
1081 activity.deleteContact();
1082 break;
1083 case R.id.context_join_conference:
1084 activity.openConversationForBookmark();
1085 break;
1086 case R.id.context_delete_conference:
1087 activity.deleteConference();
1088 }
1089 return true;
1090 }
1091 }
1092
1093 private class Invite extends XmppUri {
1094
1095 public Invite(final Uri uri) {
1096 super(uri);
1097 }
1098
1099 public Invite(final String uri) {
1100 super(uri);
1101 }
1102
1103 boolean invite() {
1104 if (getJid() != null) {
1105 return handleJid(this);
1106 }
1107 return false;
1108 }
1109
1110 public boolean isMuc() {
1111 return muc;
1112 }
1113 }
1114}