StartConversationActivity.java

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