StartConversationActivity.java

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