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