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