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 (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
 844        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
 845            xmppConnectionService.getQuickConversationsService().considerSyncBackground(false);
 846        }
 847        if (mPostponedActivityResult != null) {
 848            onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
 849            this.mPostponedActivityResult = null;
 850        }
 851        this.mActivatedAccounts.clear();
 852        this.mActivatedAccounts.addAll(AccountUtils.getEnabledAccounts(xmppConnectionService));
 853        configureHomeButton();
 854        Intent intent = pendingViewIntent.pop();
 855        if (intent != null && processViewIntent(intent)) {
 856            filter(null);
 857        } else {
 858            if (mSearchEditText != null) {
 859                filter(mSearchEditText.getText().toString());
 860            } else {
 861                filter(null);
 862            }
 863        }
 864        Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 865        if (fragment instanceof OnBackendConnected) {
 866            Log.d(Config.LOGTAG, "calling on backend connected on dialog");
 867            ((OnBackendConnected) fragment).onBackendConnected();
 868        }
 869        if (QuickConversationsService.isQuicksy()) {
 870            setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing());
 871        }
 872        if (QuickConversationsService.isConversations() && AccountUtils.hasEnabledAccounts(xmppConnectionService) && this.contacts.size() == 0 && this.conferences.size() == 0 && mOpenedFab.compareAndSet(false, true)) {
 873            binding.speedDial.open();
 874        }
 875    }
 876
 877    protected boolean processViewIntent(@NonNull Intent intent) {
 878        final String inviteUri = intent.getStringExtra(EXTRA_INVITE_URI);
 879        if (inviteUri != null) {
 880            final Invite invite = new Invite(inviteUri);
 881            invite.account = intent.getStringExtra(EXTRA_ACCOUNT);
 882            if (invite.isValidJid()) {
 883                return invite.invite();
 884            }
 885        }
 886        final String action = intent.getAction();
 887        if (action == null) {
 888            return false;
 889        }
 890        switch (action) {
 891            case Intent.ACTION_SENDTO:
 892            case Intent.ACTION_VIEW:
 893                Uri uri = intent.getData();
 894                if (uri != null) {
 895                    Invite invite = new Invite(intent.getData(), intent.getBooleanExtra("scanned", false));
 896                    invite.account = intent.getStringExtra(EXTRA_ACCOUNT);
 897                    invite.forceDialog = intent.getBooleanExtra("force_dialog", false);
 898                    return invite.invite();
 899                } else {
 900                    return false;
 901                }
 902        }
 903        return false;
 904    }
 905
 906    private boolean handleJid(Invite invite) {
 907        List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid(), invite.account);
 908        if (invite.isAction(XmppUri.ACTION_JOIN)) {
 909            Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
 910            if (muc != null && !invite.forceDialog) {
 911                switchToConversationDoNotAppend(muc, invite.getBody());
 912                return true;
 913            } else {
 914                showJoinConferenceDialog(invite.getJid().asBareJid().toEscapedString());
 915                return false;
 916            }
 917        } else if (contacts.size() == 0) {
 918            showCreateContactDialog(invite.getJid().toEscapedString(), invite);
 919            return false;
 920        } else if (contacts.size() == 1) {
 921            Contact contact = contacts.get(0);
 922            if (!invite.isSafeSource() && invite.hasFingerprints()) {
 923                displayVerificationWarningDialog(contact, invite);
 924            } else {
 925                if (invite.hasFingerprints()) {
 926                    if (xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints())) {
 927                        Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
 928                    }
 929                }
 930                if (invite.account != null) {
 931                    xmppConnectionService.getShortcutService().report(contact);
 932                }
 933                switchToConversationDoNotAppend(contact, invite.getBody());
 934            }
 935            return true;
 936        } else {
 937            if (mMenuSearchView != null) {
 938                mMenuSearchView.expandActionView();
 939                mSearchEditText.setText("");
 940                mSearchEditText.append(invite.getJid().toEscapedString());
 941                filter(invite.getJid().toEscapedString());
 942            } else {
 943                mInitialSearchValue.push(invite.getJid().toEscapedString());
 944            }
 945            return true;
 946        }
 947    }
 948
 949    private void displayVerificationWarningDialog(final Contact contact, final Invite invite) {
 950        AlertDialog.Builder builder = new AlertDialog.Builder(this);
 951        builder.setTitle(R.string.verify_omemo_keys);
 952        View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
 953        final CheckBox isTrustedSource = view.findViewById(R.id.trusted_source);
 954        TextView warning = view.findViewById(R.id.warning);
 955        warning.setText(JidDialog.style(this, R.string.verifying_omemo_keys_trusted_source, contact.getJid().asBareJid().toEscapedString(), contact.getDisplayName()));
 956        builder.setView(view);
 957        builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
 958            if (isTrustedSource.isChecked() && invite.hasFingerprints()) {
 959                xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
 960            }
 961            switchToConversationDoNotAppend(contact, invite.getBody());
 962        });
 963        builder.setNegativeButton(R.string.cancel, (dialog, which) -> StartConversationActivity.this.finish());
 964        AlertDialog dialog = builder.create();
 965        dialog.setCanceledOnTouchOutside(false);
 966        dialog.setOnCancelListener(dialog1 -> StartConversationActivity.this.finish());
 967        dialog.show();
 968    }
 969
 970    protected void filter(String needle) {
 971        if (xmppConnectionServiceBound) {
 972            this.filterContacts(needle);
 973            this.filterConferences(needle);
 974        }
 975    }
 976
 977    protected void filterContacts(String needle) {
 978        this.contacts.clear();
 979        final List<Account> accounts = xmppConnectionService.getAccounts();
 980        for (final Account account : accounts) {
 981            if (account.isEnabled()) {
 982                for (Contact contact : account.getRoster().getContacts()) {
 983                    Presence.Status s = contact.getShownStatus();
 984                    if (contact.showInContactList() && contact.match(this, needle)
 985                            && (!this.mHideOfflineContacts
 986                            || (needle != null && !needle.trim().isEmpty())
 987                            || s.compareTo(Presence.Status.OFFLINE) < 0)) {
 988                        this.contacts.add(contact);
 989                    }
 990                }
 991            }
 992        }
 993        Collections.sort(this.contacts);
 994        mContactsAdapter.notifyDataSetChanged();
 995    }
 996
 997    protected void filterConferences(String needle) {
 998        this.conferences.clear();
 999        for (final Account account : xmppConnectionService.getAccounts()) {
1000            if (account.isEnabled()) {
1001                for (final Bookmark bookmark : account.getBookmarks()) {
1002                    if (bookmark.match(this, needle)) {
1003                        this.conferences.add(bookmark);
1004                    }
1005                }
1006            }
1007        }
1008        Collections.sort(this.conferences);
1009        mConferenceAdapter.notifyDataSetChanged();
1010    }
1011
1012    @Override
1013    public void OnUpdateBlocklist(final Status status) {
1014        refreshUi();
1015    }
1016
1017    @Override
1018    protected void refreshUiReal() {
1019        if (mSearchEditText != null) {
1020            filter(mSearchEditText.getText().toString());
1021        }
1022        configureHomeButton();
1023        if (QuickConversationsService.isQuicksy()) {
1024            setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing());
1025        }
1026    }
1027
1028    @Override
1029    public void onBackPressed() {
1030        if (binding.speedDial.isOpen()) {
1031            binding.speedDial.close();
1032            return;
1033        }
1034        navigateBack();
1035    }
1036
1037    private void navigateBack() {
1038        if (!createdByViewIntent && xmppConnectionService != null && !xmppConnectionService.isConversationsListEmpty(null)) {
1039            Intent intent = new Intent(this, ConversationsActivity.class);
1040            intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
1041            startActivity(intent);
1042        }
1043        finish();
1044    }
1045
1046    @Override
1047    public void onCreateDialogPositiveClick(Spinner spinner, String name) {
1048        if (!xmppConnectionServiceBound) {
1049            return;
1050        }
1051        final Account account = getSelectedAccount(this, spinner);
1052        if (account == null) {
1053            return;
1054        }
1055        Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class);
1056        intent.putExtra(ChooseContactActivity.EXTRA_SHOW_ENTER_JID, false);
1057        intent.putExtra(ChooseContactActivity.EXTRA_SELECT_MULTIPLE, true);
1058        intent.putExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME, name.trim());
1059        intent.putExtra(ChooseContactActivity.EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
1060        intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants);
1061        startActivityForResult(intent, REQUEST_CREATE_CONFERENCE);
1062    }
1063
1064    @Override
1065    public void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, TextInputLayout layout, AutoCompleteTextView jid, boolean isBookmarkChecked) {
1066        if (!xmppConnectionServiceBound) {
1067            return;
1068        }
1069        final Account account = getSelectedAccount(this, spinner);
1070        if (account == null) {
1071            return;
1072        }
1073        final String input = jid.getText().toString().trim();
1074        Jid conferenceJid;
1075        try {
1076            conferenceJid = Jid.ofEscaped(input);
1077        } catch (final IllegalArgumentException e) {
1078            final XmppUri xmppUri = new XmppUri(input);
1079            if (xmppUri.isValidJid() && xmppUri.isAction(XmppUri.ACTION_JOIN)) {
1080                final Editable editable = jid.getEditableText();
1081                editable.clear();
1082                editable.append(xmppUri.getJid().toEscapedString());
1083                conferenceJid = xmppUri.getJid();
1084            } else {
1085                layout.setError(getString(R.string.invalid_jid));
1086                return;
1087            }
1088        }
1089
1090        if (isBookmarkChecked) {
1091            Bookmark bookmark = account.getBookmark(conferenceJid);
1092            if (bookmark != null) {
1093                dialog.dismiss();
1094                openConversationsForBookmark(bookmark);
1095            } else {
1096                bookmark = new Bookmark(account, conferenceJid.asBareJid());
1097                bookmark.setAutojoin(getBooleanPreference("autojoin", R.bool.autojoin));
1098                final String nick = conferenceJid.getResource();
1099                if (nick != null && !nick.isEmpty() && !nick.equals(MucOptions.defaultNick(account))) {
1100                    bookmark.setNick(nick);
1101                }
1102                xmppConnectionService.createBookmark(account, bookmark);
1103                final Conversation conversation = xmppConnectionService
1104                        .findOrCreateConversation(account, conferenceJid, true, true, true);
1105                bookmark.setConversation(conversation);
1106                dialog.dismiss();
1107                switchToConversation(conversation);
1108            }
1109        } else {
1110            final Conversation conversation = xmppConnectionService
1111                    .findOrCreateConversation(account, conferenceJid, true, true, true);
1112            dialog.dismiss();
1113            switchToConversation(conversation);
1114        }
1115    }
1116
1117    @Override
1118    public void onConversationUpdate() {
1119        refreshUi();
1120    }
1121
1122    @Override
1123    public void onRefresh() {
1124        Log.d(Config.LOGTAG, "user requested to refresh");
1125        if (QuickConversationsService.isQuicksy() && xmppConnectionService != null) {
1126            xmppConnectionService.getQuickConversationsService().considerSyncBackground(true);
1127        }
1128    }
1129
1130
1131    private void setRefreshing(boolean refreshing) {
1132        MyListFragment fragment = (MyListFragment) mListPagerAdapter.getItem(0);
1133        if (fragment != null) {
1134            fragment.setRefreshing(refreshing);
1135        }
1136    }
1137
1138    @Override
1139    public void onCreatePublicChannel(Account account, String name, Jid address) {
1140        mToast = Toast.makeText(this, R.string.creating_channel, Toast.LENGTH_LONG);
1141        mToast.show();
1142        xmppConnectionService.createPublicChannel(account, name, address, new UiCallback<Conversation>() {
1143            @Override
1144            public void success(Conversation conversation) {
1145                runOnUiThread(() -> {
1146                    hideToast();
1147                    switchToConversation(conversation);
1148                });
1149
1150            }
1151
1152            @Override
1153            public void error(int errorCode, Conversation conversation) {
1154                runOnUiThread(() -> {
1155                    replaceToast(getString(errorCode));
1156                    switchToConversation(conversation);
1157                });
1158            }
1159
1160            @Override
1161            public void userInputRequired(PendingIntent pi, Conversation object) {
1162
1163            }
1164        });
1165    }
1166
1167    public static class MyListFragment extends SwipeRefreshListFragment {
1168        private AdapterView.OnItemClickListener mOnItemClickListener;
1169        private int mResContextMenu;
1170
1171        public void setContextMenu(final int res) {
1172            this.mResContextMenu = res;
1173        }
1174
1175        @Override
1176        public void onListItemClick(final ListView l, final View v, final int position, final long id) {
1177            if (mOnItemClickListener != null) {
1178                mOnItemClickListener.onItemClick(l, v, position, id);
1179            }
1180        }
1181
1182        public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
1183            this.mOnItemClickListener = l;
1184        }
1185
1186        @Override
1187        public void onViewCreated(@NonNull final View view, final Bundle savedInstanceState) {
1188            super.onViewCreated(view, savedInstanceState);
1189            registerForContextMenu(getListView());
1190            getListView().setFastScrollEnabled(true);
1191            getListView().setDivider(null);
1192            getListView().setDividerHeight(0);
1193        }
1194
1195        @Override
1196        public void onCreateContextMenu(final ContextMenu menu, final View v, final ContextMenuInfo menuInfo) {
1197            super.onCreateContextMenu(menu, v, menuInfo);
1198            final StartConversationActivity activity = (StartConversationActivity) getActivity();
1199            if (activity == null) {
1200                return;
1201            }
1202            activity.getMenuInflater().inflate(mResContextMenu, menu);
1203            final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
1204            if (mResContextMenu == R.menu.conference_context) {
1205                activity.conference_context_id = acmi.position;
1206                final Bookmark bookmark = (Bookmark) activity.conferences.get(acmi.position);
1207                final Conversation conversation = bookmark.getConversation();
1208                final MenuItem share = menu.findItem(R.id.context_share_uri);
1209                share.setVisible(conversation == null || !conversation.isPrivateAndNonAnonymous());
1210            } else if (mResContextMenu == R.menu.contact_context) {
1211                activity.contact_context_id = acmi.position;
1212                final Contact contact = (Contact) activity.contacts.get(acmi.position);
1213                final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
1214                final MenuItem showContactDetailsItem = menu.findItem(R.id.context_contact_details);
1215                final MenuItem deleteContactMenuItem = menu.findItem(R.id.context_delete_contact);
1216                if (contact.isSelf()) {
1217                    showContactDetailsItem.setVisible(false);
1218                }
1219                deleteContactMenuItem.setVisible(contact.showInRoster() && !contact.getOption(Contact.Options.SYNCED_VIA_OTHER));
1220                final XmppConnection xmpp = contact.getAccount().getXmppConnection();
1221                if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) {
1222                    if (contact.isBlocked()) {
1223                        blockUnblockItem.setTitle(R.string.unblock_contact);
1224                    } else {
1225                        blockUnblockItem.setTitle(R.string.block_contact);
1226                    }
1227                } else {
1228                    blockUnblockItem.setVisible(false);
1229                }
1230            }
1231        }
1232
1233        @Override
1234        public boolean onContextItemSelected(final MenuItem item) {
1235            StartConversationActivity activity = (StartConversationActivity) getActivity();
1236            if (activity == null) {
1237                return true;
1238            }
1239            switch (item.getItemId()) {
1240                case R.id.context_contact_details:
1241                    activity.openDetailsForContact();
1242                    break;
1243                case R.id.context_show_qr:
1244                    activity.showQrForContact();
1245                    break;
1246                case R.id.context_contact_block_unblock:
1247                    activity.toggleContactBlock();
1248                    break;
1249                case R.id.context_delete_contact:
1250                    activity.deleteContact();
1251                    break;
1252                case R.id.context_share_uri:
1253                    activity.shareBookmarkUri();
1254                    break;
1255                case R.id.context_delete_conference:
1256                    activity.deleteConference();
1257            }
1258            return true;
1259        }
1260    }
1261
1262    public class ListPagerAdapter extends PagerAdapter {
1263        private final FragmentManager fragmentManager;
1264        private final MyListFragment[] fragments;
1265
1266        ListPagerAdapter(FragmentManager fm) {
1267            fragmentManager = fm;
1268            fragments = new MyListFragment[2];
1269        }
1270
1271        public void requestFocus(int pos) {
1272            if (fragments.length > pos) {
1273                fragments[pos].getListView().requestFocus();
1274            }
1275        }
1276
1277        @Override
1278        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
1279            FragmentTransaction trans = fragmentManager.beginTransaction();
1280            trans.remove(fragments[position]);
1281            trans.commit();
1282            fragments[position] = null;
1283        }
1284
1285        @NonNull
1286        @Override
1287        public Fragment instantiateItem(@NonNull ViewGroup container, int position) {
1288            final Fragment fragment = getItem(position);
1289            final FragmentTransaction trans = fragmentManager.beginTransaction();
1290            trans.add(container.getId(), fragment, "fragment:" + position);
1291            try {
1292                trans.commit();
1293            } catch (IllegalStateException e) {
1294                //ignore
1295            }
1296            return fragment;
1297        }
1298
1299        @Override
1300        public int getCount() {
1301            return fragments.length;
1302        }
1303
1304        @Override
1305        public boolean isViewFromObject(@NonNull View view, @NonNull Object fragment) {
1306            return ((Fragment) fragment).getView() == view;
1307        }
1308
1309        @Nullable
1310        @Override
1311        public CharSequence getPageTitle(int position) {
1312            switch (position) {
1313                case 0:
1314                    return getResources().getString(R.string.contacts);
1315                case 1:
1316                    return getResources().getString(R.string.group_chats);
1317                default:
1318                    return super.getPageTitle(position);
1319            }
1320        }
1321
1322        Fragment getItem(int position) {
1323            if (fragments[position] == null) {
1324                final MyListFragment listFragment = new MyListFragment();
1325                if (position == 1) {
1326                    listFragment.setListAdapter(mConferenceAdapter);
1327                    listFragment.setContextMenu(R.menu.conference_context);
1328                    listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForBookmark(p));
1329                } else {
1330                    listFragment.setListAdapter(mContactsAdapter);
1331                    listFragment.setContextMenu(R.menu.contact_context);
1332                    listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForContact(p));
1333                    if (QuickConversationsService.isQuicksy()) {
1334                        listFragment.setOnRefreshListener(StartConversationActivity.this);
1335                    }
1336                }
1337                fragments[position] = listFragment;
1338            }
1339            return fragments[position];
1340        }
1341    }
1342
1343    public static void addInviteUri(Intent to, Intent from) {
1344        if (from != null && from.hasExtra(EXTRA_INVITE_URI)) {
1345            final String invite = from.getStringExtra(EXTRA_INVITE_URI);
1346            to.putExtra(EXTRA_INVITE_URI, invite);
1347        }
1348    }
1349
1350    private class Invite extends XmppUri {
1351
1352        public String account;
1353
1354        boolean forceDialog = false;
1355
1356
1357        Invite(final String uri) {
1358            super(uri);
1359        }
1360
1361        Invite(Uri uri, boolean safeSource) {
1362            super(uri, safeSource);
1363        }
1364
1365        boolean invite() {
1366            if (!isValidJid()) {
1367                Toast.makeText(StartConversationActivity.this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
1368                return false;
1369            }
1370            if (getJid() != null) {
1371                return handleJid(this);
1372            }
1373            return false;
1374        }
1375    }
1376}