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