StartConversationActivity.java

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