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