StartConversationActivity.java

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