StartConversationActivity.java

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