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.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}