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        int position = conference_context_id;
 506        final Bookmark bookmark = (Bookmark) conferences.get(position);
 507
 508        AlertDialog.Builder builder = new AlertDialog.Builder(this);
 509        builder.setNegativeButton(R.string.cancel, null);
 510        builder.setTitle(R.string.delete_bookmark);
 511        builder.setMessage(JidDialog.style(this, R.string.remove_bookmark_text, bookmark.getJid().toEscapedString()));
 512        builder.setPositiveButton(R.string.delete, (dialog, which) -> {
 513            bookmark.setConversation(null);
 514            final Account account = bookmark.getAccount();
 515            xmppConnectionService.deleteBookmark(account, bookmark);
 516            filter(mSearchEditText.getText().toString());
 517        });
 518        builder.create().show();
 519
 520    }
 521
 522    @SuppressLint("InflateParams")
 523    protected void showCreateContactDialog(final String prefilledJid, final Invite invite) {
 524        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 525        Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 526        if (prev != null) {
 527            ft.remove(prev);
 528        }
 529        ft.addToBackStack(null);
 530        EnterJidDialog dialog = EnterJidDialog.newInstance(
 531                mActivatedAccounts,
 532                getString(R.string.add_contact),
 533                getString(R.string.add),
 534                prefilledJid,
 535                invite == null ? null : invite.account,
 536                invite == null || !invite.hasFingerprints(),
 537                true
 538        );
 539
 540        dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> {
 541            if (!xmppConnectionServiceBound) {
 542                return false;
 543            }
 544
 545            final Account account = xmppConnectionService.findAccountByJid(accountJid);
 546            if (account == null) {
 547                return true;
 548            }
 549
 550            final Contact contact = account.getRoster().getContact(contactJid);
 551            if (invite != null && invite.getName() != null) {
 552                contact.setServerName(invite.getName());
 553            }
 554            if (contact.isSelf()) {
 555                switchToConversation(contact);
 556                return true;
 557            } else if (contact.showInRoster()) {
 558                throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists));
 559            } else {
 560                final String preAuth = invite == null ? null : invite.getParameter(XmppUri.PARAMETER_PRE_AUTH);
 561                xmppConnectionService.createContact(contact, true, preAuth);
 562                if (invite != null && invite.hasFingerprints()) {
 563                    xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
 564                }
 565                switchToConversationDoNotAppend(contact, invite == null ? null : invite.getBody());
 566                return true;
 567            }
 568        });
 569        dialog.show(ft, FRAGMENT_TAG_DIALOG);
 570    }
 571
 572    @SuppressLint("InflateParams")
 573    protected void showJoinConferenceDialog(final String prefilledJid) {
 574        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 575        Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 576        if (prev != null) {
 577            ft.remove(prev);
 578        }
 579        ft.addToBackStack(null);
 580        JoinConferenceDialog joinConferenceFragment = JoinConferenceDialog.newInstance(prefilledJid, mActivatedAccounts);
 581        joinConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
 582    }
 583
 584    private void showCreatePrivateGroupChatDialog() {
 585        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 586        Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 587        if (prev != null) {
 588            ft.remove(prev);
 589        }
 590        ft.addToBackStack(null);
 591        CreatePrivateGroupChatDialog createConferenceFragment = CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts);
 592        createConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
 593    }
 594
 595    private void showPublicChannelDialog() {
 596        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 597        Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 598        if (prev != null) {
 599            ft.remove(prev);
 600        }
 601        ft.addToBackStack(null);
 602        CreatePublicChannelDialog dialog = CreatePublicChannelDialog.newInstance(mActivatedAccounts);
 603        dialog.show(ft, FRAGMENT_TAG_DIALOG);
 604    }
 605
 606    public static Account getSelectedAccount(Context context, Spinner spinner) {
 607        if (spinner == null || !spinner.isEnabled()) {
 608            return null;
 609        }
 610        if (context instanceof XmppActivity) {
 611            Jid jid;
 612            try {
 613                if (Config.DOMAIN_LOCK != null) {
 614                    jid = Jid.ofEscaped((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
 615                } else {
 616                    jid = Jid.ofEscaped((String) spinner.getSelectedItem());
 617                }
 618            } catch (final IllegalArgumentException e) {
 619                return null;
 620            }
 621            final XmppConnectionService service = ((XmppActivity) context).xmppConnectionService;
 622            if (service == null) {
 623                return null;
 624            }
 625            return service.findAccountByJid(jid);
 626        } else {
 627            return null;
 628        }
 629    }
 630
 631    protected void switchToConversation(Contact contact) {
 632        Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
 633        switchToConversation(conversation);
 634    }
 635
 636    protected void switchToConversationDoNotAppend(Contact contact, String body) {
 637        Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
 638        switchToConversationDoNotAppend(conversation, body);
 639    }
 640
 641    @Override
 642    public void invalidateOptionsMenu() {
 643        boolean isExpanded = mMenuSearchView != null && mMenuSearchView.isActionViewExpanded();
 644        String text = mSearchEditText != null ? mSearchEditText.getText().toString() : "";
 645        if (isExpanded) {
 646            mInitialSearchValue.push(text);
 647            oneShotKeyboardSuppress.set(true);
 648        }
 649        super.invalidateOptionsMenu();
 650    }
 651
 652    private void updateSearchViewHint() {
 653        if (binding == null || mSearchEditText == null) {
 654            return;
 655        }
 656        if (binding.startConversationViewPager.getCurrentItem() == 0) {
 657            mSearchEditText.setHint(R.string.search_contacts);
 658            mSearchEditText.setContentDescription(getString(R.string.search_contacts));
 659        } else {
 660            mSearchEditText.setHint(R.string.search_group_chats);
 661            mSearchEditText.setContentDescription(getString(R.string.search_group_chats));
 662        }
 663    }
 664
 665    @Override
 666    public boolean onCreateOptionsMenu(final Menu menu) {
 667        getMenuInflater().inflate(R.menu.start_conversation, menu);
 668        AccountUtils.showHideMenuItems(menu);
 669        final MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline);
 670        final MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code);
 671        final MenuItem privacyPolicyMenuItem = menu.findItem(R.id.action_privacy_policy);
 672        privacyPolicyMenuItem.setVisible(
 673                BuildConfig.PRIVACY_POLICY != null
 674                        && QuickConversationsService.isPlayStoreFlavor());
 675        qrCodeScanMenuItem.setVisible(isCameraFeatureAvailable());
 676        if (QuickConversationsService.isQuicksy()) {
 677            menuHideOffline.setVisible(false);
 678        } else {
 679            menuHideOffline.setVisible(true);
 680            menuHideOffline.setChecked(this.mHideOfflineContacts);
 681        }
 682        mMenuSearchView = menu.findItem(R.id.action_search);
 683        mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
 684        View mSearchView = mMenuSearchView.getActionView();
 685        mSearchEditText = mSearchView.findViewById(R.id.search_field);
 686        mSearchEditText.addTextChangedListener(mSearchTextWatcher);
 687        mSearchEditText.setOnEditorActionListener(mSearchDone);
 688        String initialSearchValue = mInitialSearchValue.pop();
 689        if (initialSearchValue != null) {
 690            mMenuSearchView.expandActionView();
 691            mSearchEditText.append(initialSearchValue);
 692            filter(initialSearchValue);
 693        }
 694        updateSearchViewHint();
 695        return super.onCreateOptionsMenu(menu);
 696    }
 697
 698    @Override
 699    public boolean onOptionsItemSelected(MenuItem item) {
 700        if (MenuDoubleTabUtil.shouldIgnoreTap()) {
 701            return false;
 702        }
 703        switch (item.getItemId()) {
 704            case android.R.id.home:
 705                navigateBack();
 706                return true;
 707            case R.id.action_scan_qr_code:
 708                UriHandlerActivity.scan(this);
 709                return true;
 710            case R.id.action_hide_offline:
 711                mHideOfflineContacts = !item.isChecked();
 712                getPreferences().edit().putBoolean("hide_offline", mHideOfflineContacts).apply();
 713                if (mSearchEditText != null) {
 714                    filter(mSearchEditText.getText().toString());
 715                }
 716                invalidateOptionsMenu();
 717        }
 718        return super.onOptionsItemSelected(item);
 719    }
 720
 721    @Override
 722    public boolean onKeyUp(int keyCode, KeyEvent event) {
 723        if (keyCode == KeyEvent.KEYCODE_SEARCH && !event.isLongPress()) {
 724            openSearch();
 725            return true;
 726        }
 727        int c = event.getUnicodeChar();
 728        if (c > 32) {
 729            if (mSearchEditText != null && !mSearchEditText.isFocused()) {
 730                openSearch();
 731                mSearchEditText.append(Character.toString((char) c));
 732                return true;
 733            }
 734        }
 735        return super.onKeyUp(keyCode, event);
 736    }
 737
 738    private void openSearch() {
 739        if (mMenuSearchView != null) {
 740            mMenuSearchView.expandActionView();
 741        }
 742    }
 743
 744    @Override
 745    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
 746        if (resultCode == RESULT_OK) {
 747            if (xmppConnectionServiceBound) {
 748                this.mPostponedActivityResult = null;
 749                if (requestCode == REQUEST_CREATE_CONFERENCE) {
 750                    Account account = extractAccount(intent);
 751                    final String name = intent.getStringExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME);
 752                    final List<Jid> jids = ChooseContactActivity.extractJabberIds(intent);
 753                    if (account != null && jids.size() > 0) {
 754                        if (xmppConnectionService.createAdhocConference(account, name, jids, mAdhocConferenceCallback)) {
 755                            mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
 756                            mToast.show();
 757                        }
 758                    }
 759                }
 760            } else {
 761                this.mPostponedActivityResult = new Pair<>(requestCode, intent);
 762            }
 763        }
 764        super.onActivityResult(requestCode, requestCode, intent);
 765    }
 766
 767    private void askForContactsPermissions() {
 768        if (QuickConversationsService.isContactListIntegration(this)
 769                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 770            if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
 771                    != PackageManager.PERMISSION_GRANTED) {
 772                if (mRequestedContactsPermission.compareAndSet(false, true)) {
 773                    final String consent =
 774                            PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
 775                                    .getString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, null);
 776                    final boolean requiresConsent =
 777                            (QuickConversationsService.isQuicksy()
 778                                            || QuickConversationsService.isPlayStoreFlavor())
 779                                    && !"agreed".equals(consent);
 780                    if (requiresConsent && "declined".equals(consent)) {
 781                        Log.d(Config.LOGTAG,"not asking for contacts permission because consent has been declined");
 782                        return;
 783                    }
 784                    if (requiresConsent
 785                            || shouldShowRequestPermissionRationale(
 786                                    Manifest.permission.READ_CONTACTS)) {
 787                        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
 788                        final AtomicBoolean requestPermission = new AtomicBoolean(false);
 789                        if (QuickConversationsService.isQuicksy()) {
 790                            builder.setTitle(R.string.quicksy_wants_your_consent);
 791                            builder.setMessage(
 792                                    Html.fromHtml(
 793                                            getString(R.string.sync_with_contacts_quicksy_static)));
 794                        } else {
 795                            builder.setTitle(R.string.sync_with_contacts);
 796                            builder.setMessage(
 797                                    getString(
 798                                            R.string.sync_with_contacts_long,
 799                                            getString(R.string.app_name)));
 800                        }
 801                        @StringRes int confirmButtonText;
 802                        if (requiresConsent) {
 803                            confirmButtonText = R.string.agree_and_continue;
 804                        } else {
 805                            confirmButtonText = R.string.next;
 806                        }
 807                        builder.setPositiveButton(
 808                                confirmButtonText,
 809                                (dialog, which) -> {
 810                                    if (requiresConsent) {
 811                                        PreferenceManager.getDefaultSharedPreferences(
 812                                                        getApplicationContext())
 813                                                .edit()
 814                                                .putString(
 815                                                        PREF_KEY_CONTACT_INTEGRATION_CONSENT, "agreed")
 816                                                .apply();
 817                                    }
 818                                    if (requestPermission.compareAndSet(false, true)) {
 819                                        requestPermissions(
 820                                                new String[] {Manifest.permission.READ_CONTACTS},
 821                                                REQUEST_SYNC_CONTACTS);
 822                                    }
 823                                });
 824                        if (requiresConsent) {
 825                            builder.setNegativeButton(R.string.decline, (dialog, which) -> PreferenceManager.getDefaultSharedPreferences(
 826                                            getApplicationContext())
 827                                    .edit()
 828                                    .putString(
 829                                            PREF_KEY_CONTACT_INTEGRATION_CONSENT, "declined")
 830                                    .apply());
 831                        } else {
 832                            builder.setOnDismissListener(
 833                                    dialog -> {
 834                                        if (requestPermission.compareAndSet(false, true)) {
 835                                            requestPermissions(
 836                                                    new String[] {
 837                                                        Manifest.permission.READ_CONTACTS
 838                                                    },
 839                                                    REQUEST_SYNC_CONTACTS);
 840                                        }
 841                                    });
 842                        }
 843                        builder.setCancelable(requiresConsent);
 844                        final AlertDialog dialog = builder.create();
 845                        dialog.setCanceledOnTouchOutside(requiresConsent);
 846                        dialog.setOnShowListener(
 847                                dialogInterface -> {
 848                                    final TextView tv = dialog.findViewById(android.R.id.message);
 849                                    if (tv != null) {
 850                                        tv.setMovementMethod(LinkMovementMethod.getInstance());
 851                                    }
 852                                });
 853                        dialog.show();
 854                    } else {
 855                        requestPermissions(
 856                                new String[] {Manifest.permission.READ_CONTACTS},
 857                                REQUEST_SYNC_CONTACTS);
 858                    }
 859                }
 860            }
 861        }
 862    }
 863
 864    @Override
 865    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
 866        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
 867        if (grantResults.length > 0)
 868            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 869                ScanActivity.onRequestPermissionResult(this, requestCode, grantResults);
 870                if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
 871                    if (QuickConversationsService.isQuicksy()) {
 872                        setRefreshing(true);
 873                    }
 874                    xmppConnectionService.loadPhoneContacts();
 875                    xmppConnectionService.startContactObserver();
 876                }
 877            }
 878    }
 879
 880    private void configureHomeButton() {
 881        final ActionBar actionBar = getSupportActionBar();
 882        if (actionBar == null) {
 883            return;
 884        }
 885        boolean openConversations = !createdByViewIntent && !xmppConnectionService.isConversationsListEmpty(null);
 886        actionBar.setDisplayHomeAsUpEnabled(openConversations);
 887        actionBar.setDisplayHomeAsUpEnabled(openConversations);
 888
 889    }
 890
 891    @Override
 892    protected void onBackendConnected() {
 893        if (QuickConversationsService.isContactListIntegration(this)
 894                && (Build.VERSION.SDK_INT < Build.VERSION_CODES.M
 895                        || checkSelfPermission(Manifest.permission.READ_CONTACTS)
 896                                == PackageManager.PERMISSION_GRANTED)) {
 897            xmppConnectionService.getQuickConversationsService().considerSyncBackground(false);
 898        }
 899        if (mPostponedActivityResult != null) {
 900            onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
 901            this.mPostponedActivityResult = null;
 902        }
 903        this.mActivatedAccounts.clear();
 904        this.mActivatedAccounts.addAll(AccountUtils.getEnabledAccounts(xmppConnectionService));
 905        configureHomeButton();
 906        Intent intent = pendingViewIntent.pop();
 907        if (intent != null && processViewIntent(intent)) {
 908            filter(null);
 909        } else {
 910            if (mSearchEditText != null) {
 911                filter(mSearchEditText.getText().toString());
 912            } else {
 913                filter(null);
 914            }
 915        }
 916        Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 917        if (fragment instanceof OnBackendConnected) {
 918            Log.d(Config.LOGTAG, "calling on backend connected on dialog");
 919            ((OnBackendConnected) fragment).onBackendConnected();
 920        }
 921        if (QuickConversationsService.isQuicksy()) {
 922            setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing());
 923        }
 924        if (QuickConversationsService.isConversations() && AccountUtils.hasEnabledAccounts(xmppConnectionService) && this.contacts.size() == 0 && this.conferences.size() == 0 && mOpenedFab.compareAndSet(false, true)) {
 925            binding.speedDial.open();
 926        }
 927    }
 928
 929    protected boolean processViewIntent(@NonNull Intent intent) {
 930        final String inviteUri = intent.getStringExtra(EXTRA_INVITE_URI);
 931        if (inviteUri != null) {
 932            final Invite invite = new Invite(inviteUri);
 933            invite.account = intent.getStringExtra(EXTRA_ACCOUNT);
 934            if (invite.isValidJid()) {
 935                return invite.invite();
 936            }
 937        }
 938        final String action = intent.getAction();
 939        if (action == null) {
 940            return false;
 941        }
 942        switch (action) {
 943            case Intent.ACTION_SENDTO:
 944            case Intent.ACTION_VIEW:
 945                Uri uri = intent.getData();
 946                if (uri != null) {
 947                    Invite invite = new Invite(intent.getData(), intent.getBooleanExtra("scanned", false));
 948                    invite.account = intent.getStringExtra(EXTRA_ACCOUNT);
 949                    invite.forceDialog = intent.getBooleanExtra("force_dialog", false);
 950                    return invite.invite();
 951                } else {
 952                    return false;
 953                }
 954        }
 955        return false;
 956    }
 957
 958    private boolean handleJid(Invite invite) {
 959        List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid(), invite.account);
 960        if (invite.isAction(XmppUri.ACTION_JOIN)) {
 961            Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
 962            if (muc != null && !invite.forceDialog) {
 963                switchToConversationDoNotAppend(muc, invite.getBody());
 964                return true;
 965            } else {
 966                showJoinConferenceDialog(invite.getJid().asBareJid().toEscapedString());
 967                return false;
 968            }
 969        } else if (contacts.size() == 0) {
 970            showCreateContactDialog(invite.getJid().toEscapedString(), invite);
 971            return false;
 972        } else if (contacts.size() == 1) {
 973            Contact contact = contacts.get(0);
 974            if (!invite.isSafeSource() && invite.hasFingerprints()) {
 975                displayVerificationWarningDialog(contact, invite);
 976            } else {
 977                if (invite.hasFingerprints()) {
 978                    if (xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints())) {
 979                        Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
 980                    }
 981                }
 982                if (invite.account != null) {
 983                    xmppConnectionService.getShortcutService().report(contact);
 984                }
 985                switchToConversationDoNotAppend(contact, invite.getBody());
 986            }
 987            return true;
 988        } else {
 989            if (mMenuSearchView != null) {
 990                mMenuSearchView.expandActionView();
 991                mSearchEditText.setText("");
 992                mSearchEditText.append(invite.getJid().toEscapedString());
 993                filter(invite.getJid().toEscapedString());
 994            } else {
 995                mInitialSearchValue.push(invite.getJid().toEscapedString());
 996            }
 997            return true;
 998        }
 999    }
1000
1001    private void displayVerificationWarningDialog(final Contact contact, final Invite invite) {
1002        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1003        builder.setTitle(R.string.verify_omemo_keys);
1004        View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
1005        final CheckBox isTrustedSource = view.findViewById(R.id.trusted_source);
1006        TextView warning = view.findViewById(R.id.warning);
1007        warning.setText(JidDialog.style(this, R.string.verifying_omemo_keys_trusted_source, contact.getJid().asBareJid().toEscapedString(), contact.getDisplayName()));
1008        builder.setView(view);
1009        builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
1010            if (isTrustedSource.isChecked() && invite.hasFingerprints()) {
1011                xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
1012            }
1013            switchToConversationDoNotAppend(contact, invite.getBody());
1014        });
1015        builder.setNegativeButton(R.string.cancel, (dialog, which) -> StartConversationActivity.this.finish());
1016        AlertDialog dialog = builder.create();
1017        dialog.setCanceledOnTouchOutside(false);
1018        dialog.setOnCancelListener(dialog1 -> StartConversationActivity.this.finish());
1019        dialog.show();
1020    }
1021
1022    protected void filter(String needle) {
1023        if (xmppConnectionServiceBound) {
1024            this.filterContacts(needle);
1025            this.filterConferences(needle);
1026        }
1027    }
1028
1029    protected void filterContacts(String needle) {
1030        this.contacts.clear();
1031        final List<Account> accounts = xmppConnectionService.getAccounts();
1032        for (final Account account : accounts) {
1033            if (account.isEnabled()) {
1034                for (Contact contact : account.getRoster().getContacts()) {
1035                    Presence.Status s = contact.getShownStatus();
1036                    if (contact.showInContactList() && contact.match(this, needle)
1037                            && (!this.mHideOfflineContacts
1038                            || (needle != null && !needle.trim().isEmpty())
1039                            || s.compareTo(Presence.Status.OFFLINE) < 0)) {
1040                        this.contacts.add(contact);
1041                    }
1042                }
1043            }
1044        }
1045        Collections.sort(this.contacts);
1046        mContactsAdapter.notifyDataSetChanged();
1047    }
1048
1049    protected void filterConferences(String needle) {
1050        this.conferences.clear();
1051        for (final Account account : xmppConnectionService.getAccounts()) {
1052            if (account.isEnabled()) {
1053                for (final Bookmark bookmark : account.getBookmarks()) {
1054                    if (bookmark.match(this, needle)) {
1055                        this.conferences.add(bookmark);
1056                    }
1057                }
1058            }
1059        }
1060        Collections.sort(this.conferences);
1061        mConferenceAdapter.notifyDataSetChanged();
1062    }
1063
1064    @Override
1065    public void OnUpdateBlocklist(final Status status) {
1066        refreshUi();
1067    }
1068
1069    @Override
1070    protected void refreshUiReal() {
1071        if (mSearchEditText != null) {
1072            filter(mSearchEditText.getText().toString());
1073        }
1074        configureHomeButton();
1075        if (QuickConversationsService.isQuicksy()) {
1076            setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing());
1077        }
1078    }
1079
1080    @Override
1081    public void onBackPressed() {
1082        if (binding.speedDial.isOpen()) {
1083            binding.speedDial.close();
1084            return;
1085        }
1086        navigateBack();
1087    }
1088
1089    private void navigateBack() {
1090        if (!createdByViewIntent && xmppConnectionService != null && !xmppConnectionService.isConversationsListEmpty(null)) {
1091            Intent intent = new Intent(this, ConversationsActivity.class);
1092            intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
1093            startActivity(intent);
1094        }
1095        finish();
1096    }
1097
1098    @Override
1099    public void onCreateDialogPositiveClick(Spinner spinner, String name) {
1100        if (!xmppConnectionServiceBound) {
1101            return;
1102        }
1103        final Account account = getSelectedAccount(this, spinner);
1104        if (account == null) {
1105            return;
1106        }
1107        Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class);
1108        intent.putExtra(ChooseContactActivity.EXTRA_SHOW_ENTER_JID, false);
1109        intent.putExtra(ChooseContactActivity.EXTRA_SELECT_MULTIPLE, true);
1110        intent.putExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME, name.trim());
1111        intent.putExtra(ChooseContactActivity.EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
1112        intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants);
1113        startActivityForResult(intent, REQUEST_CREATE_CONFERENCE);
1114    }
1115
1116    @Override
1117    public void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, TextInputLayout layout, AutoCompleteTextView jid, boolean isBookmarkChecked) {
1118        if (!xmppConnectionServiceBound) {
1119            return;
1120        }
1121        final Account account = getSelectedAccount(this, spinner);
1122        if (account == null) {
1123            return;
1124        }
1125        final String input = jid.getText().toString().trim();
1126        Jid conferenceJid;
1127        try {
1128            conferenceJid = Jid.ofEscaped(input);
1129        } catch (final IllegalArgumentException e) {
1130            final XmppUri xmppUri = new XmppUri(input);
1131            if (xmppUri.isValidJid() && xmppUri.isAction(XmppUri.ACTION_JOIN)) {
1132                final Editable editable = jid.getEditableText();
1133                editable.clear();
1134                editable.append(xmppUri.getJid().toEscapedString());
1135                conferenceJid = xmppUri.getJid();
1136            } else {
1137                layout.setError(getString(R.string.invalid_jid));
1138                return;
1139            }
1140        }
1141
1142        if (isBookmarkChecked) {
1143            Bookmark bookmark = account.getBookmark(conferenceJid);
1144            if (bookmark != null) {
1145                dialog.dismiss();
1146                openConversationsForBookmark(bookmark);
1147            } else {
1148                bookmark = new Bookmark(account, conferenceJid.asBareJid());
1149                bookmark.setAutojoin(getBooleanPreference("autojoin", R.bool.autojoin));
1150                final String nick = conferenceJid.getResource();
1151                if (nick != null && !nick.isEmpty() && !nick.equals(MucOptions.defaultNick(account))) {
1152                    bookmark.setNick(nick);
1153                }
1154                xmppConnectionService.createBookmark(account, bookmark);
1155                final Conversation conversation = xmppConnectionService
1156                        .findOrCreateConversation(account, conferenceJid, true, true, true);
1157                bookmark.setConversation(conversation);
1158                dialog.dismiss();
1159                switchToConversation(conversation);
1160            }
1161        } else {
1162            final Conversation conversation = xmppConnectionService
1163                    .findOrCreateConversation(account, conferenceJid, true, true, true);
1164            dialog.dismiss();
1165            switchToConversation(conversation);
1166        }
1167    }
1168
1169    @Override
1170    public void onConversationUpdate() {
1171        refreshUi();
1172    }
1173
1174    @Override
1175    public void onRefresh() {
1176        Log.d(Config.LOGTAG, "user requested to refresh");
1177        if (QuickConversationsService.isQuicksy() && xmppConnectionService != null) {
1178            xmppConnectionService.getQuickConversationsService().considerSyncBackground(true);
1179        }
1180    }
1181
1182
1183    private void setRefreshing(boolean refreshing) {
1184        MyListFragment fragment = (MyListFragment) mListPagerAdapter.getItem(0);
1185        if (fragment != null) {
1186            fragment.setRefreshing(refreshing);
1187        }
1188    }
1189
1190    @Override
1191    public void onCreatePublicChannel(Account account, String name, Jid address) {
1192        mToast = Toast.makeText(this, R.string.creating_channel, Toast.LENGTH_LONG);
1193        mToast.show();
1194        xmppConnectionService.createPublicChannel(account, name, address, new UiCallback<Conversation>() {
1195            @Override
1196            public void success(Conversation conversation) {
1197                runOnUiThread(() -> {
1198                    hideToast();
1199                    switchToConversation(conversation);
1200                });
1201
1202            }
1203
1204            @Override
1205            public void error(int errorCode, Conversation conversation) {
1206                runOnUiThread(() -> {
1207                    replaceToast(getString(errorCode));
1208                    switchToConversation(conversation);
1209                });
1210            }
1211
1212            @Override
1213            public void userInputRequired(PendingIntent pi, Conversation object) {
1214
1215            }
1216        });
1217    }
1218
1219    public static class MyListFragment extends SwipeRefreshListFragment {
1220        private AdapterView.OnItemClickListener mOnItemClickListener;
1221        private int mResContextMenu;
1222
1223        public void setContextMenu(final int res) {
1224            this.mResContextMenu = res;
1225        }
1226
1227        @Override
1228        public void onListItemClick(final ListView l, final View v, final int position, final long id) {
1229            if (mOnItemClickListener != null) {
1230                mOnItemClickListener.onItemClick(l, v, position, id);
1231            }
1232        }
1233
1234        public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
1235            this.mOnItemClickListener = l;
1236        }
1237
1238        @Override
1239        public void onViewCreated(@NonNull final View view, final Bundle savedInstanceState) {
1240            super.onViewCreated(view, savedInstanceState);
1241            registerForContextMenu(getListView());
1242            getListView().setFastScrollEnabled(true);
1243            getListView().setDivider(null);
1244            getListView().setDividerHeight(0);
1245        }
1246
1247        @Override
1248        public void onCreateContextMenu(final ContextMenu menu, final View v, final ContextMenuInfo menuInfo) {
1249            super.onCreateContextMenu(menu, v, menuInfo);
1250            final StartConversationActivity activity = (StartConversationActivity) getActivity();
1251            if (activity == null) {
1252                return;
1253            }
1254            activity.getMenuInflater().inflate(mResContextMenu, menu);
1255            final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
1256            if (mResContextMenu == R.menu.conference_context) {
1257                activity.conference_context_id = acmi.position;
1258                final Bookmark bookmark = (Bookmark) activity.conferences.get(acmi.position);
1259                final Conversation conversation = bookmark.getConversation();
1260                final MenuItem share = menu.findItem(R.id.context_share_uri);
1261                share.setVisible(conversation == null || !conversation.isPrivateAndNonAnonymous());
1262            } else if (mResContextMenu == R.menu.contact_context) {
1263                activity.contact_context_id = acmi.position;
1264                final Contact contact = (Contact) activity.contacts.get(acmi.position);
1265                final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
1266                final MenuItem showContactDetailsItem = menu.findItem(R.id.context_contact_details);
1267                final MenuItem deleteContactMenuItem = menu.findItem(R.id.context_delete_contact);
1268                if (contact.isSelf()) {
1269                    showContactDetailsItem.setVisible(false);
1270                }
1271                deleteContactMenuItem.setVisible(contact.showInRoster() && !contact.getOption(Contact.Options.SYNCED_VIA_OTHER));
1272                final XmppConnection xmpp = contact.getAccount().getXmppConnection();
1273                if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) {
1274                    if (contact.isBlocked()) {
1275                        blockUnblockItem.setTitle(R.string.unblock_contact);
1276                    } else {
1277                        blockUnblockItem.setTitle(R.string.block_contact);
1278                    }
1279                } else {
1280                    blockUnblockItem.setVisible(false);
1281                }
1282            }
1283        }
1284
1285        @Override
1286        public boolean onContextItemSelected(final MenuItem item) {
1287            StartConversationActivity activity = (StartConversationActivity) getActivity();
1288            if (activity == null) {
1289                return true;
1290            }
1291            switch (item.getItemId()) {
1292                case R.id.context_contact_details:
1293                    activity.openDetailsForContact();
1294                    break;
1295                case R.id.context_show_qr:
1296                    activity.showQrForContact();
1297                    break;
1298                case R.id.context_contact_block_unblock:
1299                    activity.toggleContactBlock();
1300                    break;
1301                case R.id.context_delete_contact:
1302                    activity.deleteContact();
1303                    break;
1304                case R.id.context_share_uri:
1305                    activity.shareBookmarkUri();
1306                    break;
1307                case R.id.context_delete_conference:
1308                    activity.deleteConference();
1309            }
1310            return true;
1311        }
1312    }
1313
1314    public class ListPagerAdapter extends PagerAdapter {
1315        private final FragmentManager fragmentManager;
1316        private final MyListFragment[] fragments;
1317
1318        ListPagerAdapter(FragmentManager fm) {
1319            fragmentManager = fm;
1320            fragments = new MyListFragment[2];
1321        }
1322
1323        public void requestFocus(int pos) {
1324            if (fragments.length > pos) {
1325                fragments[pos].getListView().requestFocus();
1326            }
1327        }
1328
1329        @Override
1330        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
1331            FragmentTransaction trans = fragmentManager.beginTransaction();
1332            trans.remove(fragments[position]);
1333            trans.commit();
1334            fragments[position] = null;
1335        }
1336
1337        @NonNull
1338        @Override
1339        public Fragment instantiateItem(@NonNull ViewGroup container, int position) {
1340            final Fragment fragment = getItem(position);
1341            final FragmentTransaction trans = fragmentManager.beginTransaction();
1342            trans.add(container.getId(), fragment, "fragment:" + position);
1343            try {
1344                trans.commit();
1345            } catch (IllegalStateException e) {
1346                //ignore
1347            }
1348            return fragment;
1349        }
1350
1351        @Override
1352        public int getCount() {
1353            return fragments.length;
1354        }
1355
1356        @Override
1357        public boolean isViewFromObject(@NonNull View view, @NonNull Object fragment) {
1358            return ((Fragment) fragment).getView() == view;
1359        }
1360
1361        @Nullable
1362        @Override
1363        public CharSequence getPageTitle(int position) {
1364            switch (position) {
1365                case 0:
1366                    return getResources().getString(R.string.contacts);
1367                case 1:
1368                    return getResources().getString(R.string.group_chats);
1369                default:
1370                    return super.getPageTitle(position);
1371            }
1372        }
1373
1374        Fragment getItem(int position) {
1375            if (fragments[position] == null) {
1376                final MyListFragment listFragment = new MyListFragment();
1377                if (position == 1) {
1378                    listFragment.setListAdapter(mConferenceAdapter);
1379                    listFragment.setContextMenu(R.menu.conference_context);
1380                    listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForBookmark(p));
1381                } else {
1382                    listFragment.setListAdapter(mContactsAdapter);
1383                    listFragment.setContextMenu(R.menu.contact_context);
1384                    listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForContact(p));
1385                    if (QuickConversationsService.isQuicksy()) {
1386                        listFragment.setOnRefreshListener(StartConversationActivity.this);
1387                    }
1388                }
1389                fragments[position] = listFragment;
1390            }
1391            return fragments[position];
1392        }
1393    }
1394
1395    public static void addInviteUri(Intent to, Intent from) {
1396        if (from != null && from.hasExtra(EXTRA_INVITE_URI)) {
1397            final String invite = from.getStringExtra(EXTRA_INVITE_URI);
1398            to.putExtra(EXTRA_INVITE_URI, invite);
1399        }
1400    }
1401
1402    private class Invite extends XmppUri {
1403
1404        public String account;
1405
1406        boolean forceDialog = false;
1407
1408
1409        Invite(final String uri) {
1410            super(uri);
1411        }
1412
1413        Invite(Uri uri, boolean safeSource) {
1414            super(uri, safeSource);
1415        }
1416
1417        boolean invite() {
1418            if (!isValidJid()) {
1419                Toast.makeText(StartConversationActivity.this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
1420                return false;
1421            }
1422            if (getJid() != null) {
1423                return handleJid(this);
1424            }
1425            return false;
1426        }
1427    }
1428}