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                        if (xmppConnectionService.createAdhocConference(account, subject, jids, mAdhocConferenceCallback)) {
 723                            mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
 724                            mToast.show();
 725                        }
 726                    }
 727                }
 728            } else {
 729                this.mPostponedActivityResult = new Pair<>(requestCode, intent);
 730            }
 731        }
 732        super.onActivityResult(requestCode, requestCode, intent);
 733    }
 734
 735    private void askForContactsPermissions() {
 736        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 737            if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
 738                if (mRequestedContactsPermission.compareAndSet(false, true)) {
 739                    if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
 740                        AlertDialog.Builder builder = new AlertDialog.Builder(this);
 741                        builder.setTitle(R.string.sync_with_contacts);
 742                        builder.setMessage(R.string.sync_with_contacts_long);
 743                        builder.setPositiveButton(R.string.next, new OnClickListener() {
 744                            @Override
 745                            public void onClick(DialogInterface dialog, int which) {
 746                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 747                                    requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
 748                                }
 749                            }
 750                        });
 751                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
 752                            builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
 753                                @Override
 754                                public void onDismiss(DialogInterface dialog) {
 755                                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 756                                        requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
 757                                    }
 758                                }
 759                            });
 760                        }
 761                        builder.create().show();
 762                    } else {
 763                        requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 0);
 764                    }
 765                }
 766            }
 767        }
 768    }
 769
 770    @Override
 771    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
 772        if (grantResults.length > 0)
 773            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 774                if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
 775                    xmppConnectionService.loadPhoneContacts();
 776                }
 777            }
 778    }
 779
 780    @Override
 781    protected void onBackendConnected() {
 782        if (mPostponedActivityResult != null) {
 783            onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
 784            this.mPostponedActivityResult = null;
 785        }
 786        this.mActivatedAccounts.clear();
 787        for (Account account : xmppConnectionService.getAccounts()) {
 788            if (account.getStatus() != Account.State.DISABLED) {
 789                if (Config.DOMAIN_LOCK != null) {
 790                    this.mActivatedAccounts.add(account.getJid().getLocalpart());
 791                } else {
 792                    this.mActivatedAccounts.add(account.getJid().toBareJid().toString());
 793                }
 794            }
 795        }
 796        final Intent intent = getIntent();
 797        final ActionBar ab = getActionBar();
 798        boolean init = intent != null && intent.getBooleanExtra("init", false);
 799        boolean noConversations = xmppConnectionService.getConversations().size() == 0;
 800        if ((init || noConversations) && ab != null) {
 801            ab.setDisplayShowHomeEnabled(false);
 802            ab.setDisplayHomeAsUpEnabled(false);
 803            ab.setHomeButtonEnabled(false);
 804        }
 805        this.mKnownHosts = xmppConnectionService.getKnownHosts();
 806        this.mKnownConferenceHosts = xmppConnectionService.getKnownConferenceHosts();
 807        if (this.mPendingInvite != null) {
 808            mPendingInvite.invite();
 809            this.mPendingInvite = null;
 810            filter(null);
 811        } else if (!handleIntent(getIntent())) {
 812            if (mSearchEditText != null) {
 813                filter(mSearchEditText.getText().toString());
 814            } else {
 815                filter(null);
 816            }
 817        } else {
 818            filter(null);
 819        }
 820        setIntent(null);
 821    }
 822
 823    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
 824    Invite getInviteJellyBean(NdefRecord record) {
 825        return new Invite(record.toUri());
 826    }
 827
 828    protected boolean handleIntent(Intent intent) {
 829        if (intent == null || intent.getAction() == null) {
 830            return false;
 831        }
 832        switch (intent.getAction()) {
 833            case Intent.ACTION_SENDTO:
 834            case Intent.ACTION_VIEW:
 835                Uri uri = intent.getData();
 836                if (uri != null) {
 837                    return new Invite(intent.getData(),false).invite();
 838                } else {
 839                    return false;
 840                }
 841            case NfcAdapter.ACTION_NDEF_DISCOVERED:
 842                for (Parcelable message : getIntent().getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
 843                    if (message instanceof NdefMessage) {
 844                        for (NdefRecord record : ((NdefMessage) message).getRecords()) {
 845                            switch (record.getTnf()) {
 846                                case NdefRecord.TNF_WELL_KNOWN:
 847                                    if (Arrays.equals(record.getType(), NdefRecord.RTD_URI)) {
 848                                        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
 849                                            return getInviteJellyBean(record).invite();
 850                                        } else {
 851                                            byte[] payload = record.getPayload();
 852                                            if (payload[0] == 0) {
 853                                                return new Invite(Uri.parse(new String(Arrays.copyOfRange(
 854                                                        payload, 1, payload.length)))).invite();
 855                                            }
 856                                        }
 857                                    }
 858                            }
 859                        }
 860                    }
 861                }
 862        }
 863        return false;
 864    }
 865
 866    private boolean handleJid(Invite invite) {
 867        Account account = xmppConnectionService.findAccountByJid(invite.getJid());
 868        if (account != null && !account.isOptionSet(Account.OPTION_DISABLED)) {
 869            if (invite.hasFingerprints() && xmppConnectionService.verifyFingerprints(account,invite.getFingerprints())) {
 870                Toast.makeText(this,R.string.verified_fingerprints,Toast.LENGTH_SHORT).show();
 871            }
 872            switchToAccount(account);
 873            finish();
 874            return true;
 875        }
 876        List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid());
 877        if (invite.isMuc()) {
 878            Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
 879            if (muc != null) {
 880                switchToConversation(muc,invite.getBody(),false);
 881                return true;
 882            } else {
 883                showJoinConferenceDialog(invite.getJid().toBareJid().toString());
 884                return false;
 885            }
 886        } else if (contacts.size() == 0) {
 887            showCreateContactDialog(invite.getJid().toString(), invite);
 888            return false;
 889        } else if (contacts.size() == 1) {
 890            Contact contact = contacts.get(0);
 891            if (!invite.isSafeSource() && invite.hasFingerprints()) {
 892                displayVerificationWarningDialog(contact,invite);
 893            } else {
 894                if (invite.hasFingerprints()) {
 895                    if(xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints())) {
 896                        Toast.makeText(this,R.string.verified_fingerprints,Toast.LENGTH_SHORT).show();
 897                    }
 898                }
 899                switchToConversation(contact, invite.getBody());
 900            }
 901            return true;
 902        } else {
 903            if (mMenuSearchView != null) {
 904                mMenuSearchView.expandActionView();
 905                mSearchEditText.setText("");
 906                mSearchEditText.append(invite.getJid().toString());
 907                filter(invite.getJid().toString());
 908            } else {
 909                mInitialJid = invite.getJid().toString();
 910            }
 911            return true;
 912        }
 913    }
 914
 915    private void displayVerificationWarningDialog(final Contact contact, final Invite invite) {
 916        AlertDialog.Builder builder = new AlertDialog.Builder(this);
 917        builder.setTitle(R.string.verify_omemo_keys);
 918        View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
 919        final CheckBox isTrustedSource = (CheckBox) view.findViewById(R.id.trusted_source);
 920        TextView warning = (TextView) view.findViewById(R.id.warning);
 921        String jid = contact.getJid().toBareJid().toString();
 922        SpannableString spannable = new SpannableString(getString(R.string.verifying_omemo_keys_trusted_source,jid,contact.getDisplayName()));
 923        int start = spannable.toString().indexOf(jid);
 924        if (start >= 0) {
 925            spannable.setSpan(new TypefaceSpan("monospace"),start,start + jid.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 926        }
 927        warning.setText(spannable);
 928        builder.setView(view);
 929        builder.setPositiveButton(R.string.confirm, new OnClickListener() {
 930            @Override
 931            public void onClick(DialogInterface dialog, int which) {
 932                if (isTrustedSource.isChecked() && invite.hasFingerprints()) {
 933                    xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
 934                }
 935                switchToConversation(contact, invite.getBody());
 936            }
 937        });
 938        builder.setNegativeButton(R.string.cancel, new OnClickListener() {
 939            @Override
 940            public void onClick(DialogInterface dialog, int which) {
 941                StartConversationActivity.this.finish();
 942            }
 943        });
 944        AlertDialog dialog = builder.create();
 945        dialog.setCanceledOnTouchOutside(false);
 946        dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
 947            @Override
 948            public void onCancel(DialogInterface dialog) {
 949                StartConversationActivity.this.finish();
 950            }
 951        });
 952        dialog.show();
 953    }
 954
 955    protected void filter(String needle) {
 956        if (xmppConnectionServiceBound) {
 957            this.filterContacts(needle);
 958            this.filterConferences(needle);
 959        }
 960    }
 961
 962    protected void filterContacts(String needle) {
 963        this.contacts.clear();
 964        for (Account account : xmppConnectionService.getAccounts()) {
 965            if (account.getStatus() != Account.State.DISABLED) {
 966                for (Contact contact : account.getRoster().getContacts()) {
 967                    Presence.Status s = contact.getShownStatus();
 968                    if (contact.showInRoster() && contact.match(this, needle)
 969                            && (!this.mHideOfflineContacts
 970                            || (needle != null && !needle.trim().isEmpty())
 971                            || s.compareTo(Presence.Status.OFFLINE) < 0)) {
 972                        this.contacts.add(contact);
 973                    }
 974                }
 975            }
 976        }
 977        Collections.sort(this.contacts);
 978        mContactsAdapter.notifyDataSetChanged();
 979    }
 980
 981    protected void filterConferences(String needle) {
 982        this.conferences.clear();
 983        for (Account account : xmppConnectionService.getAccounts()) {
 984            if (account.getStatus() != Account.State.DISABLED) {
 985                for (Bookmark bookmark : account.getBookmarks()) {
 986                    if (bookmark.match(this, needle)) {
 987                        this.conferences.add(bookmark);
 988                    }
 989                }
 990            }
 991        }
 992        Collections.sort(this.conferences);
 993        mConferenceAdapter.notifyDataSetChanged();
 994    }
 995
 996    private void onTabChanged() {
 997        invalidateOptionsMenu();
 998    }
 999
1000    @Override
1001    public void OnUpdateBlocklist(final Status status) {
1002        refreshUi();
1003    }
1004
1005    @Override
1006    protected void refreshUiReal() {
1007        if (mSearchEditText != null) {
1008            filter(mSearchEditText.getText().toString());
1009        }
1010    }
1011
1012    public class ListPagerAdapter extends PagerAdapter {
1013        FragmentManager fragmentManager;
1014        MyListFragment[] fragments;
1015
1016        public ListPagerAdapter(FragmentManager fm) {
1017            fragmentManager = fm;
1018            fragments = new MyListFragment[2];
1019        }
1020
1021        public void requestFocus(int pos) {
1022            if (fragments.length > pos) {
1023                fragments[pos].getListView().requestFocus();
1024            }
1025        }
1026
1027        @Override
1028        public void destroyItem(ViewGroup container, int position, Object object) {
1029            assert (0 <= position && position < fragments.length);
1030            FragmentTransaction trans = fragmentManager.beginTransaction();
1031            trans.remove(fragments[position]);
1032            trans.commit();
1033            fragments[position] = null;
1034        }
1035
1036        @Override
1037        public Fragment instantiateItem(ViewGroup container, int position) {
1038            Fragment fragment = getItem(position);
1039            FragmentTransaction trans = fragmentManager.beginTransaction();
1040            trans.add(container.getId(), fragment, "fragment:" + position);
1041            trans.commit();
1042            return fragment;
1043        }
1044
1045        @Override
1046        public int getCount() {
1047            return fragments.length;
1048        }
1049
1050        @Override
1051        public boolean isViewFromObject(View view, Object fragment) {
1052            return ((Fragment) fragment).getView() == view;
1053        }
1054
1055        public Fragment getItem(int position) {
1056            assert (0 <= position && position < fragments.length);
1057            if (fragments[position] == null) {
1058                final MyListFragment listFragment = new MyListFragment();
1059                if (position == 1) {
1060                    listFragment.setListAdapter(mConferenceAdapter);
1061                    listFragment.setContextMenu(R.menu.conference_context);
1062                    listFragment.setOnListItemClickListener(new OnItemClickListener() {
1063
1064                        @Override
1065                        public void onItemClick(AdapterView<?> arg0, View arg1,
1066                                                int position, long arg3) {
1067                            openConversationForBookmark(position);
1068                        }
1069                    });
1070                } else {
1071
1072                    listFragment.setListAdapter(mContactsAdapter);
1073                    listFragment.setContextMenu(R.menu.contact_context);
1074                    listFragment.setOnListItemClickListener(new OnItemClickListener() {
1075
1076                        @Override
1077                        public void onItemClick(AdapterView<?> arg0, View arg1,
1078                                                int position, long arg3) {
1079                            openConversationForContact(position);
1080                        }
1081                    });
1082                }
1083                fragments[position] = listFragment;
1084            }
1085            return fragments[position];
1086        }
1087    }
1088
1089    public static class MyListFragment extends ListFragment {
1090        private AdapterView.OnItemClickListener mOnItemClickListener;
1091        private int mResContextMenu;
1092
1093        public void setContextMenu(final int res) {
1094            this.mResContextMenu = res;
1095        }
1096
1097        @Override
1098        public void onListItemClick(final ListView l, final View v, final int position, final long id) {
1099            if (mOnItemClickListener != null) {
1100                mOnItemClickListener.onItemClick(l, v, position, id);
1101            }
1102        }
1103
1104        public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
1105            this.mOnItemClickListener = l;
1106        }
1107
1108        @Override
1109        public void onViewCreated(final View view, final Bundle savedInstanceState) {
1110            super.onViewCreated(view, savedInstanceState);
1111            registerForContextMenu(getListView());
1112            getListView().setFastScrollEnabled(true);
1113        }
1114
1115        @Override
1116        public void onCreateContextMenu(final ContextMenu menu, final View v,
1117                                        final ContextMenuInfo menuInfo) {
1118            super.onCreateContextMenu(menu, v, menuInfo);
1119            final StartConversationActivity activity = (StartConversationActivity) getActivity();
1120            activity.getMenuInflater().inflate(mResContextMenu, menu);
1121            final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
1122            if (mResContextMenu == R.menu.conference_context) {
1123                activity.conference_context_id = acmi.position;
1124            } else if (mResContextMenu == R.menu.contact_context) {
1125                activity.contact_context_id = acmi.position;
1126                final Contact contact = (Contact) activity.contacts.get(acmi.position);
1127                final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
1128                final MenuItem showContactDetailsItem = menu.findItem(R.id.context_contact_details);
1129                if (contact.isSelf()) {
1130                    showContactDetailsItem.setVisible(false);
1131                }
1132                XmppConnection xmpp = contact.getAccount().getXmppConnection();
1133                if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) {
1134                    if (contact.isBlocked()) {
1135                        blockUnblockItem.setTitle(R.string.unblock_contact);
1136                    } else {
1137                        blockUnblockItem.setTitle(R.string.block_contact);
1138                    }
1139                } else {
1140                    blockUnblockItem.setVisible(false);
1141                }
1142            }
1143        }
1144
1145        @Override
1146        public boolean onContextItemSelected(final MenuItem item) {
1147            StartConversationActivity activity = (StartConversationActivity) getActivity();
1148            switch (item.getItemId()) {
1149                case R.id.context_start_conversation:
1150                    activity.openConversationForContact();
1151                    break;
1152                case R.id.context_contact_details:
1153                    activity.openDetailsForContact();
1154                    break;
1155                case R.id.context_contact_block_unblock:
1156                    activity.toggleContactBlock();
1157                    break;
1158                case R.id.context_delete_contact:
1159                    activity.deleteContact();
1160                    break;
1161                case R.id.context_join_conference:
1162                    activity.openConversationForBookmark();
1163                    break;
1164                case R.id.context_share_uri:
1165                    activity.shareBookmarkUri();
1166                    break;
1167                case R.id.context_delete_conference:
1168                    activity.deleteConference();
1169            }
1170            return true;
1171        }
1172    }
1173
1174    private class Invite extends XmppUri {
1175
1176        public Invite(final Uri uri) {
1177            super(uri);
1178        }
1179
1180        public Invite(final String uri) {
1181            super(uri);
1182        }
1183
1184        public Invite(Uri uri, boolean safeSource) {
1185            super(uri,safeSource);
1186        }
1187
1188        boolean invite() {
1189            if (getJid() != null) {
1190                return handleJid(this);
1191            }
1192            return false;
1193        }
1194
1195        public boolean isMuc() {
1196            return muc;
1197        }
1198    }
1199}