StartConversationActivity.java

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