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