StartConversationActivity.java

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