StartConversationActivity.java

   1package eu.siacs.conversations.ui;
   2
   3import android.Manifest;
   4import android.annotation.SuppressLint;
   5import android.app.Dialog;
   6import android.app.PendingIntent;
   7import android.content.ActivityNotFoundException;
   8import android.content.Context;
   9import android.content.Intent;
  10import android.content.SharedPreferences;
  11import android.content.pm.PackageManager;
  12import android.net.Uri;
  13import android.os.Build;
  14import android.os.Bundle;
  15import android.text.Editable;
  16import android.text.Html;
  17import android.text.TextWatcher;
  18import android.text.method.LinkMovementMethod;
  19import android.util.Log;
  20import android.util.Pair;
  21import android.view.ContextMenu;
  22import android.view.ContextMenu.ContextMenuInfo;
  23import android.view.KeyEvent;
  24import android.view.Menu;
  25import android.view.MenuItem;
  26import android.view.View;
  27import android.view.ViewGroup;
  28import android.view.inputmethod.InputMethodManager;
  29import android.widget.AdapterView;
  30import android.widget.AdapterView.AdapterContextMenuInfo;
  31import android.widget.ArrayAdapter;
  32import android.widget.AutoCompleteTextView;
  33import android.widget.CheckBox;
  34import android.widget.EditText;
  35import android.widget.ListView;
  36import android.widget.Spinner;
  37import android.widget.TextView;
  38import android.widget.Toast;
  39
  40import androidx.annotation.NonNull;
  41import androidx.annotation.Nullable;
  42import androidx.annotation.StringRes;
  43import androidx.appcompat.app.ActionBar;
  44import androidx.appcompat.app.AlertDialog;
  45import androidx.databinding.DataBindingUtil;
  46import androidx.fragment.app.Fragment;
  47import androidx.fragment.app.FragmentManager;
  48import androidx.fragment.app.FragmentTransaction;
  49import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
  50import androidx.viewpager.widget.PagerAdapter;
  51import androidx.viewpager.widget.ViewPager;
  52
  53import com.google.android.material.textfield.TextInputLayout;
  54
  55import java.util.ArrayList;
  56import java.util.Collections;
  57import java.util.List;
  58import java.util.concurrent.atomic.AtomicBoolean;
  59
  60import eu.siacs.conversations.Config;
  61import eu.siacs.conversations.R;
  62import eu.siacs.conversations.databinding.ActivityStartConversationBinding;
  63import eu.siacs.conversations.entities.Account;
  64import eu.siacs.conversations.entities.Bookmark;
  65import eu.siacs.conversations.entities.Contact;
  66import eu.siacs.conversations.entities.Conversation;
  67import eu.siacs.conversations.entities.ListItem;
  68import eu.siacs.conversations.entities.MucOptions;
  69import eu.siacs.conversations.entities.Presence;
  70import eu.siacs.conversations.services.QuickConversationsService;
  71import eu.siacs.conversations.services.XmppConnectionService;
  72import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
  73import eu.siacs.conversations.ui.adapter.ListItemAdapter;
  74import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
  75import eu.siacs.conversations.ui.util.JidDialog;
  76import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
  77import eu.siacs.conversations.ui.util.PendingItem;
  78import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
  79import eu.siacs.conversations.ui.widget.SwipeRefreshListFragment;
  80import eu.siacs.conversations.utils.AccountUtils;
  81import eu.siacs.conversations.utils.XmppUri;
  82import eu.siacs.conversations.xmpp.Jid;
  83import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
  84import eu.siacs.conversations.xmpp.XmppConnection;
  85
  86public class StartConversationActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, CreatePrivateGroupChatDialog.CreateConferenceDialogListener, JoinConferenceDialog.JoinConferenceDialogListener, SwipeRefreshLayout.OnRefreshListener, CreatePublicChannelDialog.CreatePublicChannelDialogListener {
  87
  88    public static final String EXTRA_INVITE_URI = "eu.siacs.conversations.invite_uri";
  89
  90    private final int REQUEST_SYNC_CONTACTS = 0x28cf;
  91    private final int REQUEST_CREATE_CONFERENCE = 0x39da;
  92    private final PendingItem<Intent> pendingViewIntent = new PendingItem<>();
  93    private final PendingItem<String> mInitialSearchValue = new PendingItem<>();
  94    private final AtomicBoolean oneShotKeyboardSuppress = new AtomicBoolean();
  95    public int conference_context_id;
  96    public int contact_context_id;
  97    private ListPagerAdapter mListPagerAdapter;
  98    private final List<ListItem> contacts = new ArrayList<>();
  99    private ListItemAdapter mContactsAdapter;
 100    private final List<ListItem> conferences = new ArrayList<>();
 101    private ListItemAdapter mConferenceAdapter;
 102    private final List<String> mActivatedAccounts = new ArrayList<>();
 103    private EditText mSearchEditText;
 104    private final AtomicBoolean mRequestedContactsPermission = new AtomicBoolean(false);
 105    private final AtomicBoolean mOpenedFab = new AtomicBoolean(false);
 106    private boolean mHideOfflineContacts = false;
 107    private boolean createdByViewIntent = false;
 108    private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
 109
 110        @Override
 111        public boolean onMenuItemActionExpand(MenuItem item) {
 112            mSearchEditText.post(() -> {
 113                updateSearchViewHint();
 114                mSearchEditText.requestFocus();
 115                if (oneShotKeyboardSuppress.compareAndSet(true, false)) {
 116                    return;
 117                }
 118                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 119                if (imm != null) {
 120                    imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
 121                }
 122            });
 123            if (binding.speedDial.isOpen()) {
 124                binding.speedDial.close();
 125            }
 126            return true;
 127        }
 128
 129        @Override
 130        public boolean onMenuItemActionCollapse(MenuItem item) {
 131            SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this);
 132            mSearchEditText.setText("");
 133            filter(null);
 134            return true;
 135        }
 136    };
 137    private final TextWatcher mSearchTextWatcher = new TextWatcher() {
 138
 139        @Override
 140        public void afterTextChanged(Editable editable) {
 141            filter(editable.toString());
 142        }
 143
 144        @Override
 145        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
 146        }
 147
 148        @Override
 149        public void onTextChanged(CharSequence s, int start, int before, int count) {
 150        }
 151    };
 152    private MenuItem mMenuSearchView;
 153    private final ListItemAdapter.OnTagClickedListener mOnTagClickedListener = new ListItemAdapter.OnTagClickedListener() {
 154        @Override
 155        public void onTagClicked(String tag) {
 156            if (mMenuSearchView != null) {
 157                mMenuSearchView.expandActionView();
 158                mSearchEditText.setText("");
 159                mSearchEditText.append(tag);
 160                filter(tag);
 161            }
 162        }
 163    };
 164    private Pair<Integer, Intent> mPostponedActivityResult;
 165    private Toast mToast;
 166    private final UiCallback<Conversation> mAdhocConferenceCallback = new UiCallback<Conversation>() {
 167        @Override
 168        public void success(final Conversation conversation) {
 169            runOnUiThread(() -> {
 170                hideToast();
 171                switchToConversation(conversation);
 172            });
 173        }
 174
 175        @Override
 176        public void error(final int errorCode, Conversation object) {
 177            runOnUiThread(() -> replaceToast(getString(errorCode)));
 178        }
 179
 180        @Override
 181        public void userInputRequired(PendingIntent pi, Conversation object) {
 182
 183        }
 184    };
 185    private ActivityStartConversationBinding binding;
 186    private final TextView.OnEditorActionListener mSearchDone = new TextView.OnEditorActionListener() {
 187        @Override
 188        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
 189            int pos = binding.startConversationViewPager.getCurrentItem();
 190            if (pos == 0) {
 191                if (contacts.size() == 1) {
 192                    openConversationForContact((Contact) contacts.get(0));
 193                    return true;
 194                } else if (contacts.size() == 0 && conferences.size() == 1) {
 195                    openConversationsForBookmark((Bookmark) conferences.get(0));
 196                    return true;
 197                }
 198            } else {
 199                if (conferences.size() == 1) {
 200                    openConversationsForBookmark((Bookmark) conferences.get(0));
 201                    return true;
 202                } else if (conferences.size() == 0 && contacts.size() == 1) {
 203                    openConversationForContact((Contact) contacts.get(0));
 204                    return true;
 205                }
 206            }
 207            SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this);
 208            mListPagerAdapter.requestFocus(pos);
 209            return true;
 210        }
 211    };
 212
 213    public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) {
 214        if (accounts.size() > 0) {
 215            ArrayAdapter<String> adapter = new ArrayAdapter<>(context, R.layout.simple_list_item, accounts);
 216            adapter.setDropDownViewResource(R.layout.simple_list_item);
 217            spinner.setAdapter(adapter);
 218            spinner.setEnabled(true);
 219        } else {
 220            ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
 221                    R.layout.simple_list_item,
 222                    Collections.singletonList(context.getString(R.string.no_accounts)));
 223            adapter.setDropDownViewResource(R.layout.simple_list_item);
 224            spinner.setAdapter(adapter);
 225            spinner.setEnabled(false);
 226        }
 227    }
 228
 229    public static void launch(Context context) {
 230        final Intent intent = new Intent(context, StartConversationActivity.class);
 231        context.startActivity(intent);
 232    }
 233
 234    private static Intent createLauncherIntent(Context context) {
 235        final Intent intent = new Intent(context, StartConversationActivity.class);
 236        intent.setAction(Intent.ACTION_MAIN);
 237        intent.addCategory(Intent.CATEGORY_LAUNCHER);
 238        return intent;
 239    }
 240
 241    private static boolean isViewIntent(final Intent i) {
 242        return i != null && (Intent.ACTION_VIEW.equals(i.getAction()) || Intent.ACTION_SENDTO.equals(i.getAction()) || i.hasExtra(EXTRA_INVITE_URI));
 243    }
 244
 245    protected void hideToast() {
 246        if (mToast != null) {
 247            mToast.cancel();
 248        }
 249    }
 250
 251    protected void replaceToast(String msg) {
 252        hideToast();
 253        mToast = Toast.makeText(this, msg, Toast.LENGTH_LONG);
 254        mToast.show();
 255    }
 256
 257    @Override
 258    public void onRosterUpdate() {
 259        this.refreshUi();
 260    }
 261
 262    @Override
 263    public void onCreate(Bundle savedInstanceState) {
 264        super.onCreate(savedInstanceState);
 265        this.binding = DataBindingUtil.setContentView(this, R.layout.activity_start_conversation);
 266        setSupportActionBar(binding.toolbar);
 267        configureActionBar(getSupportActionBar());
 268
 269        binding.speedDial.inflate(R.menu.start_conversation_fab_submenu);
 270
 271        binding.tabLayout.setupWithViewPager(binding.startConversationViewPager);
 272        binding.startConversationViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
 273            @Override
 274            public void onPageSelected(int position) {
 275                updateSearchViewHint();
 276            }
 277        });
 278        mListPagerAdapter = new ListPagerAdapter(getSupportFragmentManager());
 279        binding.startConversationViewPager.setAdapter(mListPagerAdapter);
 280
 281        mConferenceAdapter = new ListItemAdapter(this, conferences);
 282        mContactsAdapter = new ListItemAdapter(this, contacts);
 283        mContactsAdapter.setOnTagClickedListener(this.mOnTagClickedListener);
 284
 285        final SharedPreferences preferences = getPreferences();
 286
 287        this.mHideOfflineContacts = QuickConversationsService.isConversations() && preferences.getBoolean("hide_offline", false);
 288
 289        final boolean startSearching = preferences.getBoolean("start_searching", getResources().getBoolean(R.bool.start_searching));
 290
 291        final Intent intent;
 292        if (savedInstanceState == null) {
 293            intent = getIntent();
 294        } else {
 295            createdByViewIntent = savedInstanceState.getBoolean("created_by_view_intent", false);
 296            final String search = savedInstanceState.getString("search");
 297            if (search != null) {
 298                mInitialSearchValue.push(search);
 299            }
 300            intent = savedInstanceState.getParcelable("intent");
 301        }
 302
 303        if (isViewIntent(intent)) {
 304            pendingViewIntent.push(intent);
 305            createdByViewIntent = true;
 306            setIntent(createLauncherIntent(this));
 307        } else if (startSearching && mInitialSearchValue.peek() == null) {
 308            mInitialSearchValue.push("");
 309        }
 310        mRequestedContactsPermission.set(savedInstanceState != null && savedInstanceState.getBoolean("requested_contacts_permission", false));
 311        mOpenedFab.set(savedInstanceState != null && savedInstanceState.getBoolean("opened_fab", false));
 312        binding.speedDial.setOnActionSelectedListener(actionItem -> {
 313            final String searchString = mSearchEditText != null ? mSearchEditText.getText().toString() : null;
 314            final String prefilled;
 315            if (isValidJid(searchString)) {
 316                prefilled = Jid.ofEscaped(searchString).toEscapedString();
 317            } else {
 318                prefilled = null;
 319            }
 320            switch (actionItem.getId()) {
 321                case R.id.discover_public_channels:
 322                    startActivity(new Intent(this, ChannelDiscoveryActivity.class));
 323                    break;
 324                case R.id.join_public_channel:
 325                    showJoinConferenceDialog(prefilled);
 326                    break;
 327                case R.id.create_private_group_chat:
 328                    showCreatePrivateGroupChatDialog();
 329                    break;
 330                case R.id.create_public_channel:
 331                    showPublicChannelDialog();
 332                    break;
 333                case R.id.create_contact:
 334                    showCreateContactDialog(prefilled, null);
 335                    break;
 336            }
 337            return false;
 338        });
 339    }
 340
 341    public static boolean isValidJid(String input) {
 342        try {
 343            Jid jid = Jid.ofEscaped(input);
 344            return !jid.isDomainJid();
 345        } catch (IllegalArgumentException e) {
 346            return false;
 347        }
 348    }
 349
 350    @Override
 351    public void onSaveInstanceState(Bundle savedInstanceState) {
 352        Intent pendingIntent = pendingViewIntent.peek();
 353        savedInstanceState.putParcelable("intent", pendingIntent != null ? pendingIntent : getIntent());
 354        savedInstanceState.putBoolean("requested_contacts_permission", mRequestedContactsPermission.get());
 355        savedInstanceState.putBoolean("opened_fab", mOpenedFab.get());
 356        savedInstanceState.putBoolean("created_by_view_intent", createdByViewIntent);
 357        if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) {
 358            savedInstanceState.putString("search", mSearchEditText != null ? mSearchEditText.getText().toString() : null);
 359        }
 360        super.onSaveInstanceState(savedInstanceState);
 361    }
 362
 363    @Override
 364    public void onStart() {
 365        super.onStart();
 366        final int theme = findTheme();
 367        if (this.mTheme != theme) {
 368            recreate();
 369        } else {
 370            if (pendingViewIntent.peek() == null) {
 371                askForContactsPermissions();
 372            }
 373        }
 374        mConferenceAdapter.refreshSettings();
 375        mContactsAdapter.refreshSettings();
 376    }
 377
 378    @Override
 379    public void onNewIntent(final Intent intent) {
 380        super.onNewIntent(intent);
 381        if (xmppConnectionServiceBound) {
 382            processViewIntent(intent);
 383        } else {
 384            pendingViewIntent.push(intent);
 385        }
 386        setIntent(createLauncherIntent(this));
 387    }
 388
 389    protected void openConversationForContact(int position) {
 390        Contact contact = (Contact) contacts.get(position);
 391        openConversationForContact(contact);
 392    }
 393
 394    protected void openConversationForContact(Contact contact) {
 395        Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
 396        SoftKeyboardUtils.hideSoftKeyboard(this);
 397        switchToConversation(conversation);
 398    }
 399
 400    protected void openConversationForBookmark(int position) {
 401        Bookmark bookmark = (Bookmark) conferences.get(position);
 402        openConversationsForBookmark(bookmark);
 403    }
 404
 405    protected void shareBookmarkUri() {
 406        shareBookmarkUri(conference_context_id);
 407    }
 408
 409    protected void shareBookmarkUri(int position) {
 410        Bookmark bookmark = (Bookmark) conferences.get(position);
 411        shareAsChannel(this, bookmark.getJid().asBareJid().toEscapedString());
 412    }
 413
 414    public static void shareAsChannel(final Context context, final String address) {
 415        Intent shareIntent = new Intent();
 416        shareIntent.setAction(Intent.ACTION_SEND);
 417        shareIntent.putExtra(Intent.EXTRA_TEXT, "xmpp:" + address + "?join");
 418        shareIntent.setType("text/plain");
 419        try {
 420            context.startActivity(Intent.createChooser(shareIntent, context.getText(R.string.share_uri_with)));
 421        } catch (ActivityNotFoundException e) {
 422            Toast.makeText(context, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
 423        }
 424    }
 425
 426    protected void openConversationsForBookmark(Bookmark bookmark) {
 427        final Jid jid = bookmark.getFullJid();
 428        if (jid == null) {
 429            Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
 430            return;
 431        }
 432        Conversation conversation = xmppConnectionService.findOrCreateConversation(bookmark.getAccount(), jid, true, true, true);
 433        bookmark.setConversation(conversation);
 434        if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", getResources().getBoolean(R.bool.autojoin))) {
 435            bookmark.setAutojoin(true);
 436            xmppConnectionService.createBookmark(bookmark.getAccount(), bookmark);
 437        }
 438        SoftKeyboardUtils.hideSoftKeyboard(this);
 439        switchToConversation(conversation);
 440    }
 441
 442    protected void openDetailsForContact() {
 443        int position = contact_context_id;
 444        Contact contact = (Contact) contacts.get(position);
 445        switchToContactDetails(contact);
 446    }
 447
 448    protected void showQrForContact() {
 449        int position = contact_context_id;
 450        Contact contact = (Contact) contacts.get(position);
 451        showQrCode("xmpp:" + contact.getJid().asBareJid().toEscapedString());
 452    }
 453
 454    protected void toggleContactBlock() {
 455        final int position = contact_context_id;
 456        BlockContactDialog.show(this, (Contact) contacts.get(position));
 457    }
 458
 459    protected void deleteContact() {
 460        final int position = contact_context_id;
 461        final Contact contact = (Contact) contacts.get(position);
 462        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
 463        builder.setNegativeButton(R.string.cancel, null);
 464        builder.setTitle(R.string.action_delete_contact);
 465        builder.setMessage(JidDialog.style(this, R.string.remove_contact_text, contact.getJid().toEscapedString()));
 466        builder.setPositiveButton(R.string.delete, (dialog, which) -> {
 467            xmppConnectionService.deleteContactOnServer(contact);
 468            filter(mSearchEditText.getText().toString());
 469        });
 470        builder.create().show();
 471    }
 472
 473    protected void deleteConference() {
 474        int position = conference_context_id;
 475        final Bookmark bookmark = (Bookmark) conferences.get(position);
 476
 477        AlertDialog.Builder builder = new AlertDialog.Builder(this);
 478        builder.setNegativeButton(R.string.cancel, null);
 479        builder.setTitle(R.string.delete_bookmark);
 480        builder.setMessage(JidDialog.style(this, R.string.remove_bookmark_text, bookmark.getJid().toEscapedString()));
 481        builder.setPositiveButton(R.string.delete, (dialog, which) -> {
 482            bookmark.setConversation(null);
 483            final Account account = bookmark.getAccount();
 484            xmppConnectionService.deleteBookmark(account, bookmark);
 485            filter(mSearchEditText.getText().toString());
 486        });
 487        builder.create().show();
 488
 489    }
 490
 491    @SuppressLint("InflateParams")
 492    protected void showCreateContactDialog(final String prefilledJid, final Invite invite) {
 493        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 494        Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 495        if (prev != null) {
 496            ft.remove(prev);
 497        }
 498        ft.addToBackStack(null);
 499        EnterJidDialog dialog = EnterJidDialog.newInstance(
 500                mActivatedAccounts,
 501                getString(R.string.add_contact),
 502                getString(R.string.add),
 503                prefilledJid,
 504                invite == null ? null : invite.account,
 505                invite == null || !invite.hasFingerprints(),
 506                true
 507        );
 508
 509        dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> {
 510            if (!xmppConnectionServiceBound) {
 511                return false;
 512            }
 513
 514            final Account account = xmppConnectionService.findAccountByJid(accountJid);
 515            if (account == null) {
 516                return true;
 517            }
 518
 519            final Contact contact = account.getRoster().getContact(contactJid);
 520            if (invite != null && invite.getName() != null) {
 521                contact.setServerName(invite.getName());
 522            }
 523            if (contact.isSelf()) {
 524                switchToConversation(contact);
 525                return true;
 526            } else if (contact.showInRoster()) {
 527                throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists));
 528            } else {
 529                final String preAuth = invite == null ? null : invite.getParameter(XmppUri.PARAMETER_PRE_AUTH);
 530                xmppConnectionService.createContact(contact, true, preAuth);
 531                if (invite != null && invite.hasFingerprints()) {
 532                    xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
 533                }
 534                switchToConversationDoNotAppend(contact, invite == null ? null : invite.getBody());
 535                return true;
 536            }
 537        });
 538        dialog.show(ft, FRAGMENT_TAG_DIALOG);
 539    }
 540
 541    @SuppressLint("InflateParams")
 542    protected void showJoinConferenceDialog(final String prefilledJid) {
 543        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 544        Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 545        if (prev != null) {
 546            ft.remove(prev);
 547        }
 548        ft.addToBackStack(null);
 549        JoinConferenceDialog joinConferenceFragment = JoinConferenceDialog.newInstance(prefilledJid, mActivatedAccounts);
 550        joinConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
 551    }
 552
 553    private void showCreatePrivateGroupChatDialog() {
 554        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 555        Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 556        if (prev != null) {
 557            ft.remove(prev);
 558        }
 559        ft.addToBackStack(null);
 560        CreatePrivateGroupChatDialog createConferenceFragment = CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts);
 561        createConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
 562    }
 563
 564    private void showPublicChannelDialog() {
 565        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 566        Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 567        if (prev != null) {
 568            ft.remove(prev);
 569        }
 570        ft.addToBackStack(null);
 571        CreatePublicChannelDialog dialog = CreatePublicChannelDialog.newInstance(mActivatedAccounts);
 572        dialog.show(ft, FRAGMENT_TAG_DIALOG);
 573    }
 574
 575    public static Account getSelectedAccount(Context context, Spinner spinner) {
 576        if (spinner == null || !spinner.isEnabled()) {
 577            return null;
 578        }
 579        if (context instanceof XmppActivity) {
 580            Jid jid;
 581            try {
 582                if (Config.DOMAIN_LOCK != null) {
 583                    jid = Jid.ofEscaped((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
 584                } else {
 585                    jid = Jid.ofEscaped((String) spinner.getSelectedItem());
 586                }
 587            } catch (final IllegalArgumentException e) {
 588                return null;
 589            }
 590            final XmppConnectionService service = ((XmppActivity) context).xmppConnectionService;
 591            if (service == null) {
 592                return null;
 593            }
 594            return service.findAccountByJid(jid);
 595        } else {
 596            return null;
 597        }
 598    }
 599
 600    protected void switchToConversation(Contact contact) {
 601        Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
 602        switchToConversation(conversation);
 603    }
 604
 605    protected void switchToConversationDoNotAppend(Contact contact, String body) {
 606        Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
 607        switchToConversationDoNotAppend(conversation, body);
 608    }
 609
 610    @Override
 611    public void invalidateOptionsMenu() {
 612        boolean isExpanded = mMenuSearchView != null && mMenuSearchView.isActionViewExpanded();
 613        String text = mSearchEditText != null ? mSearchEditText.getText().toString() : "";
 614        if (isExpanded) {
 615            mInitialSearchValue.push(text);
 616            oneShotKeyboardSuppress.set(true);
 617        }
 618        super.invalidateOptionsMenu();
 619    }
 620
 621    private void updateSearchViewHint() {
 622        if (binding == null || mSearchEditText == null) {
 623            return;
 624        }
 625        if (binding.startConversationViewPager.getCurrentItem() == 0) {
 626            mSearchEditText.setHint(R.string.search_contacts);
 627            mSearchEditText.setContentDescription(getString(R.string.search_contacts));
 628        } else {
 629            mSearchEditText.setHint(R.string.search_bookmarks);
 630            mSearchEditText.setContentDescription(getString(R.string.search_bookmarks));
 631        }
 632    }
 633
 634    @Override
 635    public boolean onCreateOptionsMenu(Menu menu) {
 636        getMenuInflater().inflate(R.menu.start_conversation, menu);
 637        AccountUtils.showHideMenuItems(menu);
 638        MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline);
 639        MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code);
 640        qrCodeScanMenuItem.setVisible(isCameraFeatureAvailable());
 641        if (QuickConversationsService.isQuicksy()) {
 642            menuHideOffline.setVisible(false);
 643        } else {
 644            menuHideOffline.setVisible(true);
 645            menuHideOffline.setChecked(this.mHideOfflineContacts);
 646        }
 647        mMenuSearchView = menu.findItem(R.id.action_search);
 648        mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
 649        View mSearchView = mMenuSearchView.getActionView();
 650        mSearchEditText = mSearchView.findViewById(R.id.search_field);
 651        mSearchEditText.addTextChangedListener(mSearchTextWatcher);
 652        mSearchEditText.setOnEditorActionListener(mSearchDone);
 653        String initialSearchValue = mInitialSearchValue.pop();
 654        if (initialSearchValue != null) {
 655            mMenuSearchView.expandActionView();
 656            mSearchEditText.append(initialSearchValue);
 657            filter(initialSearchValue);
 658        }
 659        updateSearchViewHint();
 660        return super.onCreateOptionsMenu(menu);
 661    }
 662
 663    @Override
 664    public boolean onOptionsItemSelected(MenuItem item) {
 665        if (MenuDoubleTabUtil.shouldIgnoreTap()) {
 666            return false;
 667        }
 668        switch (item.getItemId()) {
 669            case android.R.id.home:
 670                navigateBack();
 671                return true;
 672            case R.id.action_scan_qr_code:
 673                UriHandlerActivity.scan(this);
 674                return true;
 675            case R.id.action_hide_offline:
 676                mHideOfflineContacts = !item.isChecked();
 677                getPreferences().edit().putBoolean("hide_offline", mHideOfflineContacts).apply();
 678                if (mSearchEditText != null) {
 679                    filter(mSearchEditText.getText().toString());
 680                }
 681                invalidateOptionsMenu();
 682        }
 683        return super.onOptionsItemSelected(item);
 684    }
 685
 686    @Override
 687    public boolean onKeyUp(int keyCode, KeyEvent event) {
 688        if (keyCode == KeyEvent.KEYCODE_SEARCH && !event.isLongPress()) {
 689            openSearch();
 690            return true;
 691        }
 692        int c = event.getUnicodeChar();
 693        if (c > 32) {
 694            if (mSearchEditText != null && !mSearchEditText.isFocused()) {
 695                openSearch();
 696                mSearchEditText.append(Character.toString((char) c));
 697                return true;
 698            }
 699        }
 700        return super.onKeyUp(keyCode, event);
 701    }
 702
 703    private void openSearch() {
 704        if (mMenuSearchView != null) {
 705            mMenuSearchView.expandActionView();
 706        }
 707    }
 708
 709    @Override
 710    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
 711        if (resultCode == RESULT_OK) {
 712            if (xmppConnectionServiceBound) {
 713                this.mPostponedActivityResult = null;
 714                if (requestCode == REQUEST_CREATE_CONFERENCE) {
 715                    Account account = extractAccount(intent);
 716                    final String name = intent.getStringExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME);
 717                    final List<Jid> jids = ChooseContactActivity.extractJabberIds(intent);
 718                    if (account != null && jids.size() > 0) {
 719                        if (xmppConnectionService.createAdhocConference(account, name, jids, mAdhocConferenceCallback)) {
 720                            mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
 721                            mToast.show();
 722                        }
 723                    }
 724                }
 725            } else {
 726                this.mPostponedActivityResult = new Pair<>(requestCode, intent);
 727            }
 728        }
 729        super.onActivityResult(requestCode, requestCode, intent);
 730    }
 731
 732    private void askForContactsPermissions() {
 733        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 734            if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
 735                if (mRequestedContactsPermission.compareAndSet(false, true)) {
 736                    if (QuickConversationsService.isQuicksy() || shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
 737                        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
 738                        final AtomicBoolean requestPermission = new AtomicBoolean(false);
 739                        builder.setTitle(R.string.sync_with_contacts);
 740                        if (QuickConversationsService.isQuicksy()) {
 741                            builder.setMessage(Html.fromHtml(getString(R.string.sync_with_contacts_quicksy)));
 742                        } else {
 743                            builder.setMessage(getString(R.string.sync_with_contacts_long, getString(R.string.app_name)));
 744                        }
 745                        @StringRes int confirmButtonText;
 746                        if (QuickConversationsService.isConversations()) {
 747                            confirmButtonText = R.string.next;
 748                        } else {
 749                            confirmButtonText = R.string.confirm;
 750                        }
 751                        builder.setPositiveButton(confirmButtonText, (dialog, which) -> {
 752                            if (requestPermission.compareAndSet(false, true)) {
 753                                requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
 754                            }
 755                        });
 756                        builder.setOnDismissListener(dialog -> {
 757                            if (QuickConversationsService.isConversations() && requestPermission.compareAndSet(false, true)) {
 758                                requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
 759
 760                            }
 761                        });
 762                        builder.setCancelable(QuickConversationsService.isQuicksy());
 763                        final AlertDialog dialog = builder.create();
 764                        dialog.setCanceledOnTouchOutside(QuickConversationsService.isQuicksy());
 765                        dialog.setOnShowListener(dialogInterface -> {
 766                            final TextView tv = dialog.findViewById(android.R.id.message);
 767                            if (tv != null) {
 768                                tv.setMovementMethod(LinkMovementMethod.getInstance());
 769                            }
 770                        });
 771                        dialog.show();
 772                    } else {
 773                        requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
 774                    }
 775                }
 776            }
 777        }
 778    }
 779
 780    @Override
 781    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
 782        if (grantResults.length > 0)
 783            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 784                ScanActivity.onRequestPermissionResult(this, requestCode, grantResults);
 785                if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
 786                    if (QuickConversationsService.isQuicksy()) {
 787                        setRefreshing(true);
 788                    }
 789                    xmppConnectionService.loadPhoneContacts();
 790                    xmppConnectionService.startContactObserver();
 791                }
 792            }
 793    }
 794
 795    private void configureHomeButton() {
 796        final ActionBar actionBar = getSupportActionBar();
 797        if (actionBar == null) {
 798            return;
 799        }
 800        boolean openConversations = !createdByViewIntent && !xmppConnectionService.isConversationsListEmpty(null);
 801        actionBar.setDisplayHomeAsUpEnabled(openConversations);
 802        actionBar.setDisplayHomeAsUpEnabled(openConversations);
 803
 804    }
 805
 806    @Override
 807    protected void onBackendConnected() {
 808
 809        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
 810            xmppConnectionService.getQuickConversationsService().considerSyncBackground(false);
 811        }
 812        if (mPostponedActivityResult != null) {
 813            onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
 814            this.mPostponedActivityResult = null;
 815        }
 816        this.mActivatedAccounts.clear();
 817        this.mActivatedAccounts.addAll(AccountUtils.getEnabledAccounts(xmppConnectionService));
 818        configureHomeButton();
 819        Intent intent = pendingViewIntent.pop();
 820        if (intent != null && processViewIntent(intent)) {
 821            filter(null);
 822        } else {
 823            if (mSearchEditText != null) {
 824                filter(mSearchEditText.getText().toString());
 825            } else {
 826                filter(null);
 827            }
 828        }
 829        Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 830        if (fragment instanceof OnBackendConnected) {
 831            Log.d(Config.LOGTAG, "calling on backend connected on dialog");
 832            ((OnBackendConnected) fragment).onBackendConnected();
 833        }
 834        if (QuickConversationsService.isQuicksy()) {
 835            setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing());
 836        }
 837        if (QuickConversationsService.isConversations() && AccountUtils.hasEnabledAccounts(xmppConnectionService) && this.contacts.size() == 0 && this.conferences.size() == 0 && mOpenedFab.compareAndSet(false, true)) {
 838            binding.speedDial.open();
 839        }
 840    }
 841
 842    protected boolean processViewIntent(@NonNull Intent intent) {
 843        final String inviteUri = intent.getStringExtra(EXTRA_INVITE_URI);
 844        if (inviteUri != null) {
 845            final Invite invite = new Invite(inviteUri);
 846            invite.account = intent.getStringExtra(EXTRA_ACCOUNT);
 847            if (invite.isValidJid()) {
 848                return invite.invite();
 849            }
 850        }
 851        final String action = intent.getAction();
 852        if (action == null) {
 853            return false;
 854        }
 855        switch (action) {
 856            case Intent.ACTION_SENDTO:
 857            case Intent.ACTION_VIEW:
 858                Uri uri = intent.getData();
 859                if (uri != null) {
 860                    Invite invite = new Invite(intent.getData(), intent.getBooleanExtra("scanned", false));
 861                    invite.account = intent.getStringExtra(EXTRA_ACCOUNT);
 862                    invite.forceDialog = intent.getBooleanExtra("force_dialog", false);
 863                    return invite.invite();
 864                } else {
 865                    return false;
 866                }
 867        }
 868        return false;
 869    }
 870
 871    private boolean handleJid(Invite invite) {
 872        List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid(), invite.account);
 873        if (invite.isAction(XmppUri.ACTION_JOIN)) {
 874            Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
 875            if (muc != null && !invite.forceDialog) {
 876                switchToConversationDoNotAppend(muc, invite.getBody());
 877                return true;
 878            } else {
 879                showJoinConferenceDialog(invite.getJid().asBareJid().toEscapedString());
 880                return false;
 881            }
 882        } else if (contacts.size() == 0) {
 883            showCreateContactDialog(invite.getJid().toEscapedString(), invite);
 884            return false;
 885        } else if (contacts.size() == 1) {
 886            Contact contact = contacts.get(0);
 887            if (!invite.isSafeSource() && invite.hasFingerprints()) {
 888                displayVerificationWarningDialog(contact, invite);
 889            } else {
 890                if (invite.hasFingerprints()) {
 891                    if (xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints())) {
 892                        Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
 893                    }
 894                }
 895                if (invite.account != null) {
 896                    xmppConnectionService.getShortcutService().report(contact);
 897                }
 898                switchToConversationDoNotAppend(contact, invite.getBody());
 899            }
 900            return true;
 901        } else {
 902            if (mMenuSearchView != null) {
 903                mMenuSearchView.expandActionView();
 904                mSearchEditText.setText("");
 905                mSearchEditText.append(invite.getJid().toEscapedString());
 906                filter(invite.getJid().toEscapedString());
 907            } else {
 908                mInitialSearchValue.push(invite.getJid().toEscapedString());
 909            }
 910            return true;
 911        }
 912    }
 913
 914    private void displayVerificationWarningDialog(final Contact contact, final Invite invite) {
 915        AlertDialog.Builder builder = new AlertDialog.Builder(this);
 916        builder.setTitle(R.string.verify_omemo_keys);
 917        View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
 918        final CheckBox isTrustedSource = view.findViewById(R.id.trusted_source);
 919        TextView warning = view.findViewById(R.id.warning);
 920        warning.setText(JidDialog.style(this, R.string.verifying_omemo_keys_trusted_source, contact.getJid().asBareJid().toEscapedString(), contact.getDisplayName()));
 921        builder.setView(view);
 922        builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
 923            if (isTrustedSource.isChecked() && invite.hasFingerprints()) {
 924                xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
 925            }
 926            switchToConversationDoNotAppend(contact, invite.getBody());
 927        });
 928        builder.setNegativeButton(R.string.cancel, (dialog, which) -> StartConversationActivity.this.finish());
 929        AlertDialog dialog = builder.create();
 930        dialog.setCanceledOnTouchOutside(false);
 931        dialog.setOnCancelListener(dialog1 -> StartConversationActivity.this.finish());
 932        dialog.show();
 933    }
 934
 935    protected void filter(String needle) {
 936        if (xmppConnectionServiceBound) {
 937            this.filterContacts(needle);
 938            this.filterConferences(needle);
 939        }
 940    }
 941
 942    protected void filterContacts(String needle) {
 943        this.contacts.clear();
 944        final List<Account> accounts = xmppConnectionService.getAccounts();
 945        for (Account account : accounts) {
 946            if (account.getStatus() != Account.State.DISABLED) {
 947                for (Contact contact : account.getRoster().getContacts()) {
 948                    Presence.Status s = contact.getShownStatus();
 949                    if (contact.showInContactList() && contact.match(this, needle)
 950                            && (!this.mHideOfflineContacts
 951                            || (needle != null && !needle.trim().isEmpty())
 952                            || s.compareTo(Presence.Status.OFFLINE) < 0)) {
 953                        this.contacts.add(contact);
 954                    }
 955                }
 956            }
 957        }
 958        Collections.sort(this.contacts);
 959        mContactsAdapter.notifyDataSetChanged();
 960    }
 961
 962    protected void filterConferences(String needle) {
 963        this.conferences.clear();
 964        for (Account account : xmppConnectionService.getAccounts()) {
 965            if (account.getStatus() != Account.State.DISABLED) {
 966                for (Bookmark bookmark : account.getBookmarks()) {
 967                    if (bookmark.match(this, needle)) {
 968                        this.conferences.add(bookmark);
 969                    }
 970                }
 971            }
 972        }
 973        Collections.sort(this.conferences);
 974        mConferenceAdapter.notifyDataSetChanged();
 975    }
 976
 977    @Override
 978    public void OnUpdateBlocklist(final Status status) {
 979        refreshUi();
 980    }
 981
 982    @Override
 983    protected void refreshUiReal() {
 984        if (mSearchEditText != null) {
 985            filter(mSearchEditText.getText().toString());
 986        }
 987        configureHomeButton();
 988        if (QuickConversationsService.isQuicksy()) {
 989            setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing());
 990        }
 991    }
 992
 993    @Override
 994    public void onBackPressed() {
 995        if (binding.speedDial.isOpen()) {
 996            binding.speedDial.close();
 997            return;
 998        }
 999        navigateBack();
1000    }
1001
1002    private void navigateBack() {
1003        if (!createdByViewIntent && xmppConnectionService != null && !xmppConnectionService.isConversationsListEmpty(null)) {
1004            Intent intent = new Intent(this, ConversationsActivity.class);
1005            intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
1006            startActivity(intent);
1007        }
1008        finish();
1009    }
1010
1011    @Override
1012    public void onCreateDialogPositiveClick(Spinner spinner, String name) {
1013        if (!xmppConnectionServiceBound) {
1014            return;
1015        }
1016        final Account account = getSelectedAccount(this, spinner);
1017        if (account == null) {
1018            return;
1019        }
1020        Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class);
1021        intent.putExtra(ChooseContactActivity.EXTRA_SHOW_ENTER_JID, false);
1022        intent.putExtra(ChooseContactActivity.EXTRA_SELECT_MULTIPLE, true);
1023        intent.putExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME, name.trim());
1024        intent.putExtra(ChooseContactActivity.EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
1025        intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants);
1026        startActivityForResult(intent, REQUEST_CREATE_CONFERENCE);
1027    }
1028
1029    @Override
1030    public void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, TextInputLayout layout, AutoCompleteTextView jid, boolean isBookmarkChecked) {
1031        if (!xmppConnectionServiceBound) {
1032            return;
1033        }
1034        final Account account = getSelectedAccount(this, spinner);
1035        if (account == null) {
1036            return;
1037        }
1038        final String input = jid.getText().toString();
1039        Jid conferenceJid;
1040        try {
1041            conferenceJid = Jid.ofEscaped(input);
1042        } catch (final IllegalArgumentException e) {
1043            final XmppUri xmppUri = new XmppUri(input);
1044            if (xmppUri.isValidJid() && xmppUri.isAction(XmppUri.ACTION_JOIN)) {
1045                final Editable editable = jid.getEditableText();
1046                editable.clear();
1047                editable.append(xmppUri.getJid().toEscapedString());
1048                conferenceJid = xmppUri.getJid();
1049            } else {
1050                layout.setError(getString(R.string.invalid_jid));
1051                return;
1052            }
1053        }
1054
1055        if (isBookmarkChecked) {
1056            Bookmark bookmark = account.getBookmark(conferenceJid);
1057            if (bookmark != null) {
1058                dialog.dismiss();
1059                openConversationsForBookmark(bookmark);
1060            } else {
1061                bookmark = new Bookmark(account, conferenceJid.asBareJid());
1062                bookmark.setAutojoin(getBooleanPreference("autojoin", R.bool.autojoin));
1063                final String nick = conferenceJid.getResource();
1064                if (nick != null && !nick.isEmpty() && !nick.equals(MucOptions.defaultNick(account))) {
1065                    bookmark.setNick(nick);
1066                }
1067                xmppConnectionService.createBookmark(account, bookmark);
1068                final Conversation conversation = xmppConnectionService
1069                        .findOrCreateConversation(account, conferenceJid, true, true, true);
1070                bookmark.setConversation(conversation);
1071                dialog.dismiss();
1072                switchToConversation(conversation);
1073            }
1074        } else {
1075            final Conversation conversation = xmppConnectionService
1076                    .findOrCreateConversation(account, conferenceJid, true, true, true);
1077            dialog.dismiss();
1078            switchToConversation(conversation);
1079        }
1080    }
1081
1082    @Override
1083    public void onConversationUpdate() {
1084        refreshUi();
1085    }
1086
1087    @Override
1088    public void onRefresh() {
1089        Log.d(Config.LOGTAG, "user requested to refresh");
1090        if (QuickConversationsService.isQuicksy() && xmppConnectionService != null) {
1091            xmppConnectionService.getQuickConversationsService().considerSyncBackground(true);
1092        }
1093    }
1094
1095
1096    private void setRefreshing(boolean refreshing) {
1097        MyListFragment fragment = (MyListFragment) mListPagerAdapter.getItem(0);
1098        if (fragment != null) {
1099            fragment.setRefreshing(refreshing);
1100        }
1101    }
1102
1103    @Override
1104    public void onCreatePublicChannel(Account account, String name, Jid address) {
1105        mToast = Toast.makeText(this, R.string.creating_channel, Toast.LENGTH_LONG);
1106        mToast.show();
1107        xmppConnectionService.createPublicChannel(account, name, address, new UiCallback<Conversation>() {
1108            @Override
1109            public void success(Conversation conversation) {
1110                runOnUiThread(() -> {
1111                    hideToast();
1112                    switchToConversation(conversation);
1113                });
1114
1115            }
1116
1117            @Override
1118            public void error(int errorCode, Conversation conversation) {
1119                runOnUiThread(() -> {
1120                    replaceToast(getString(errorCode));
1121                    switchToConversation(conversation);
1122                });
1123            }
1124
1125            @Override
1126            public void userInputRequired(PendingIntent pi, Conversation object) {
1127
1128            }
1129        });
1130    }
1131
1132    public static class MyListFragment extends SwipeRefreshListFragment {
1133        private AdapterView.OnItemClickListener mOnItemClickListener;
1134        private int mResContextMenu;
1135
1136        public void setContextMenu(final int res) {
1137            this.mResContextMenu = res;
1138        }
1139
1140        @Override
1141        public void onListItemClick(final ListView l, final View v, final int position, final long id) {
1142            if (mOnItemClickListener != null) {
1143                mOnItemClickListener.onItemClick(l, v, position, id);
1144            }
1145        }
1146
1147        public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
1148            this.mOnItemClickListener = l;
1149        }
1150
1151        @Override
1152        public void onViewCreated(@NonNull final View view, final Bundle savedInstanceState) {
1153            super.onViewCreated(view, savedInstanceState);
1154            registerForContextMenu(getListView());
1155            getListView().setFastScrollEnabled(true);
1156            getListView().setDivider(null);
1157            getListView().setDividerHeight(0);
1158        }
1159
1160        @Override
1161        public void onCreateContextMenu(final ContextMenu menu, final View v, final ContextMenuInfo menuInfo) {
1162            super.onCreateContextMenu(menu, v, menuInfo);
1163            final StartConversationActivity activity = (StartConversationActivity) getActivity();
1164            if (activity == null) {
1165                return;
1166            }
1167            activity.getMenuInflater().inflate(mResContextMenu, menu);
1168            final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
1169            if (mResContextMenu == R.menu.conference_context) {
1170                activity.conference_context_id = acmi.position;
1171                final Bookmark bookmark = (Bookmark) activity.conferences.get(acmi.position);
1172                final Conversation conversation = bookmark.getConversation();
1173                final MenuItem share = menu.findItem(R.id.context_share_uri);
1174                share.setVisible(conversation == null || !conversation.isPrivateAndNonAnonymous());
1175            } else if (mResContextMenu == R.menu.contact_context) {
1176                activity.contact_context_id = acmi.position;
1177                final Contact contact = (Contact) activity.contacts.get(acmi.position);
1178                final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
1179                final MenuItem showContactDetailsItem = menu.findItem(R.id.context_contact_details);
1180                final MenuItem deleteContactMenuItem = menu.findItem(R.id.context_delete_contact);
1181                if (contact.isSelf()) {
1182                    showContactDetailsItem.setVisible(false);
1183                }
1184                deleteContactMenuItem.setVisible(contact.showInRoster() && !contact.getOption(Contact.Options.SYNCED_VIA_OTHER));
1185                final XmppConnection xmpp = contact.getAccount().getXmppConnection();
1186                if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) {
1187                    if (contact.isBlocked()) {
1188                        blockUnblockItem.setTitle(R.string.unblock_contact);
1189                    } else {
1190                        blockUnblockItem.setTitle(R.string.block_contact);
1191                    }
1192                } else {
1193                    blockUnblockItem.setVisible(false);
1194                }
1195            }
1196        }
1197
1198        @Override
1199        public boolean onContextItemSelected(final MenuItem item) {
1200            StartConversationActivity activity = (StartConversationActivity) getActivity();
1201            if (activity == null) {
1202                return true;
1203            }
1204            switch (item.getItemId()) {
1205                case R.id.context_contact_details:
1206                    activity.openDetailsForContact();
1207                    break;
1208                case R.id.context_show_qr:
1209                    activity.showQrForContact();
1210                    break;
1211                case R.id.context_contact_block_unblock:
1212                    activity.toggleContactBlock();
1213                    break;
1214                case R.id.context_delete_contact:
1215                    activity.deleteContact();
1216                    break;
1217                case R.id.context_share_uri:
1218                    activity.shareBookmarkUri();
1219                    break;
1220                case R.id.context_delete_conference:
1221                    activity.deleteConference();
1222            }
1223            return true;
1224        }
1225    }
1226
1227    public class ListPagerAdapter extends PagerAdapter {
1228        private final FragmentManager fragmentManager;
1229        private final MyListFragment[] fragments;
1230
1231        ListPagerAdapter(FragmentManager fm) {
1232            fragmentManager = fm;
1233            fragments = new MyListFragment[2];
1234        }
1235
1236        public void requestFocus(int pos) {
1237            if (fragments.length > pos) {
1238                fragments[pos].getListView().requestFocus();
1239            }
1240        }
1241
1242        @Override
1243        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
1244            FragmentTransaction trans = fragmentManager.beginTransaction();
1245            trans.remove(fragments[position]);
1246            trans.commit();
1247            fragments[position] = null;
1248        }
1249
1250        @NonNull
1251        @Override
1252        public Fragment instantiateItem(@NonNull ViewGroup container, int position) {
1253            final Fragment fragment = getItem(position);
1254            final FragmentTransaction trans = fragmentManager.beginTransaction();
1255            trans.add(container.getId(), fragment, "fragment:" + position);
1256            try {
1257                trans.commit();
1258            } catch (IllegalStateException e) {
1259                //ignore
1260            }
1261            return fragment;
1262        }
1263
1264        @Override
1265        public int getCount() {
1266            return fragments.length;
1267        }
1268
1269        @Override
1270        public boolean isViewFromObject(@NonNull View view, @NonNull Object fragment) {
1271            return ((Fragment) fragment).getView() == view;
1272        }
1273
1274        @Nullable
1275        @Override
1276        public CharSequence getPageTitle(int position) {
1277            switch (position) {
1278                case 0:
1279                    return getResources().getString(R.string.contacts);
1280                case 1:
1281                    return getResources().getString(R.string.bookmarks);
1282                default:
1283                    return super.getPageTitle(position);
1284            }
1285        }
1286
1287        Fragment getItem(int position) {
1288            if (fragments[position] == null) {
1289                final MyListFragment listFragment = new MyListFragment();
1290                if (position == 1) {
1291                    listFragment.setListAdapter(mConferenceAdapter);
1292                    listFragment.setContextMenu(R.menu.conference_context);
1293                    listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForBookmark(p));
1294                } else {
1295                    listFragment.setListAdapter(mContactsAdapter);
1296                    listFragment.setContextMenu(R.menu.contact_context);
1297                    listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForContact(p));
1298                    if (QuickConversationsService.isQuicksy()) {
1299                        listFragment.setOnRefreshListener(StartConversationActivity.this);
1300                    }
1301                }
1302                fragments[position] = listFragment;
1303            }
1304            return fragments[position];
1305        }
1306    }
1307
1308    public static void addInviteUri(Intent to, Intent from) {
1309        if (from != null && from.hasExtra(EXTRA_INVITE_URI)) {
1310            final String invite = from.getStringExtra(EXTRA_INVITE_URI);
1311            to.putExtra(EXTRA_INVITE_URI, invite);
1312        }
1313    }
1314
1315    private class Invite extends XmppUri {
1316
1317        public String account;
1318
1319        boolean forceDialog = false;
1320
1321
1322        Invite(final String uri) {
1323            super(uri);
1324        }
1325
1326        Invite(Uri uri, boolean safeSource) {
1327            super(uri, safeSource);
1328        }
1329
1330        boolean invite() {
1331            if (!isValidJid()) {
1332                Toast.makeText(StartConversationActivity.this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
1333                return false;
1334            }
1335            if (getJid() != null) {
1336                return handleJid(this);
1337            }
1338            return false;
1339        }
1340    }
1341}