StartConversationActivity.java

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