StartConversationActivity.java

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