ChooseContactActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.app.Activity;
  4import android.content.Context;
  5import android.content.Intent;
  6import android.content.SharedPreferences;
  7import android.os.Bundle;
  8import android.view.ActionMode;
  9import android.view.KeyEvent;
 10import android.view.Menu;
 11import android.view.MenuItem;
 12import android.view.SoundEffectConstants;
 13import android.view.View;
 14import android.view.inputmethod.InputMethodManager;
 15import android.widget.AbsListView.MultiChoiceModeListener;
 16import android.widget.AdapterView;
 17import android.widget.ListView;
 18import android.widget.TextView;
 19
 20import androidx.annotation.NonNull;
 21import androidx.annotation.StringRes;
 22import androidx.appcompat.app.ActionBar;
 23import androidx.fragment.app.Fragment;
 24import androidx.fragment.app.FragmentTransaction;
 25
 26import com.google.common.base.Strings;
 27
 28import java.util.ArrayList;
 29import java.util.Arrays;
 30import java.util.Collections;
 31import java.util.HashSet;
 32import java.util.List;
 33import java.util.Set;
 34
 35import eu.siacs.conversations.Config;
 36import eu.siacs.conversations.R;
 37import eu.siacs.conversations.entities.Account;
 38import eu.siacs.conversations.entities.Contact;
 39import eu.siacs.conversations.entities.Conversation;
 40import eu.siacs.conversations.entities.ListItem;
 41import eu.siacs.conversations.entities.MucOptions;
 42import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
 43import eu.siacs.conversations.ui.util.ActivityResult;
 44import eu.siacs.conversations.ui.util.PendingItem;
 45import eu.siacs.conversations.utils.XmppUri;
 46import eu.siacs.conversations.xmpp.Jid;
 47
 48public class ChooseContactActivity extends AbstractSearchableListItemActivity implements MultiChoiceModeListener, AdapterView.OnItemClickListener {
 49    public static final String EXTRA_TITLE_RES_ID = "extra_title_res_id";
 50    public static final String EXTRA_GROUP_CHAT_NAME = "extra_group_chat_name";
 51    public static final String EXTRA_SELECT_MULTIPLE = "extra_select_multiple";
 52    public static final String EXTRA_SHOW_ENTER_JID = "extra_show_enter_jid";
 53    public static final String EXTRA_CONVERSATION = "extra_conversation";
 54    private static final String EXTRA_FILTERED_CONTACTS = "extra_filtered_contacts";
 55    private final ArrayList<String> mActivatedAccounts = new ArrayList<>();
 56    private final Set<String> selected = new HashSet<>();
 57    private Set<String> filterContacts;
 58
 59    private boolean showEnterJid = false;
 60    private boolean startSearching = false;
 61    private boolean multiple = false;
 62
 63    private final PendingItem<ActivityResult> postponedActivityResult = new PendingItem<>();
 64
 65    public static Intent create(Activity activity, Conversation conversation) {
 66        final Intent intent = new Intent(activity, ChooseContactActivity.class);
 67        List<String> contacts = new ArrayList<>();
 68        if (conversation.getMode() == Conversation.MODE_MULTI) {
 69            for (MucOptions.User user : conversation.getMucOptions().getUsers(false)) {
 70                Jid jid = user.getRealJid();
 71                if (jid != null) {
 72                    contacts.add(jid.asBareJid().toString());
 73                }
 74            }
 75        } else {
 76            contacts.add(conversation.getJid().asBareJid().toString());
 77        }
 78        intent.putExtra(EXTRA_FILTERED_CONTACTS, contacts.toArray(new String[contacts.size()]));
 79        intent.putExtra(EXTRA_CONVERSATION, conversation.getUuid());
 80        intent.putExtra(EXTRA_SELECT_MULTIPLE, true);
 81        intent.putExtra(EXTRA_SHOW_ENTER_JID, true);
 82        intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toEscapedString());
 83        return intent;
 84    }
 85
 86    public static List<Jid> extractJabberIds(Intent result) {
 87        List<Jid> jabberIds = new ArrayList<>();
 88        try {
 89            if (result.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false)) {
 90                String[] toAdd = result.getStringArrayExtra("contacts");
 91                for (String item : toAdd) {
 92                    jabberIds.add(Jid.of(item));
 93                }
 94            } else {
 95                jabberIds.add(Jid.of(result.getStringExtra("contact")));
 96            }
 97            return jabberIds;
 98        } catch (IllegalArgumentException e) {
 99            return jabberIds;
100        }
101    }
102
103    @Override
104    public void onCreate(final Bundle savedInstanceState) {
105        super.onCreate(savedInstanceState);
106        filterContacts = new HashSet<>();
107        if (savedInstanceState != null) {
108            String[] selectedContacts = savedInstanceState.getStringArray("selected_contacts");
109            if (selectedContacts != null) {
110                selected.clear();
111                selected.addAll(Arrays.asList(selectedContacts));
112            }
113        }
114
115        String[] contacts = getIntent().getStringArrayExtra(EXTRA_FILTERED_CONTACTS);
116        if (contacts != null) {
117            Collections.addAll(filterContacts, contacts);
118        }
119
120        Intent intent = getIntent();
121
122        multiple = intent.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false);
123        if (multiple) {
124            getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
125            getListView().setMultiChoiceModeListener(this);
126        }
127
128        getListView().setOnItemClickListener(this);
129        this.showEnterJid = intent.getBooleanExtra(EXTRA_SHOW_ENTER_JID, false);
130        this.binding.fab.setOnClickListener(this::onFabClicked);
131        if (this.showEnterJid) {
132            this.binding.fab.show();
133        } else {
134            binding.fab.setImageResource(R.drawable.ic_navigate_next_24dp);
135        }
136
137        final SharedPreferences preferences = getPreferences();
138        this.startSearching = intent.getBooleanExtra("direct_search", false) && preferences.getBoolean("start_searching", getResources().getBoolean(R.bool.start_searching));
139
140        getListItemAdapter().refreshSettings();
141        getListItemAdapter().setOnTagClickedListener((tag) -> {
142            if (mMenuSearchView != null) {
143                mMenuSearchView.expandActionView();
144                mSearchEditText.setText("");
145                mSearchEditText.append(tag);
146                filterContacts(tag);
147            }
148        });
149    }
150
151    private void onFabClicked(View v) {
152        if (selected.isEmpty()) {
153            showEnterJidDialog(null);
154        } else {
155            submitSelection();
156        }
157    }
158
159    @Override
160    public boolean colorCodeAccounts() {
161        return mActivatedAccounts.size() > 1;
162    }
163
164    @Override
165    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
166        return false;
167    }
168
169    @Override
170    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
171        mode.setTitle(getTitleFromIntent());
172        binding.chooseContactList.setFastScrollEnabled(false);
173        binding.fab.setImageResource(R.drawable.ic_navigate_next_24dp);
174        binding.fab.show();
175        final View view = getSearchEditText();
176        final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
177        if (view != null && imm != null) {
178            imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
179        }
180        return true;
181    }
182
183    @Override
184    public void onDestroyActionMode(ActionMode mode) {
185        this.binding.fab.setImageResource(R.drawable.ic_person_add_24dp);
186        if (this.showEnterJid) {
187            this.binding.fab.show();
188        } else {
189            this.binding.fab.hide();
190        }
191        binding.chooseContactList.setFastScrollEnabled(true);
192        selected.clear();
193    }
194
195    @Override
196    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
197        return false;
198    }
199
200    private void submitSelection() {
201        final Intent request = getIntent();
202        final Intent data = new Intent();
203        data.putExtra("contacts", getSelectedContactJids());
204        data.putExtra(EXTRA_SELECT_MULTIPLE, true);
205        data.putExtra(EXTRA_ACCOUNT, request.getStringExtra(EXTRA_ACCOUNT));
206        copy(request, data);
207        setResult(RESULT_OK, data);
208        finish();
209    }
210
211    private static void copy(Intent from, Intent to) {
212        to.putExtra(EXTRA_CONVERSATION, from.getStringExtra(EXTRA_CONVERSATION));
213        to.putExtra(EXTRA_GROUP_CHAT_NAME, from.getStringExtra(EXTRA_GROUP_CHAT_NAME));
214    }
215
216    @Override
217    public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
218        if (selected.size() != 0) {
219            getListView().playSoundEffect(SoundEffectConstants.CLICK);
220        }
221        getListItemAdapter().notifyDataSetChanged();
222        Contact item = (Contact) getListItems().get(position);
223        if (checked) {
224            selected.add(item.getJid().toString());
225        } else {
226            selected.remove(item.getJid().toString());
227        }
228    }
229
230    @Override
231    public void onStart() {
232        super.onStart();
233        ActionBar bar = getSupportActionBar();
234        if (bar != null) {
235            try {
236                bar.setTitle(getTitleFromIntent());
237            } catch (Exception e) {
238                bar.setTitle(R.string.title_activity_choose_contact);
239            }
240        }
241    }
242
243    public @StringRes
244    int getTitleFromIntent() {
245        final Intent intent = getIntent();
246        boolean multiple = intent != null && intent.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false);
247        @StringRes int fallback = multiple ? R.string.title_activity_choose_contacts : R.string.title_activity_choose_contact;
248        return intent != null ? intent.getIntExtra(EXTRA_TITLE_RES_ID, fallback) : fallback;
249    }
250
251    @Override
252    public boolean onCreateOptionsMenu(final Menu menu) {
253        super.onCreateOptionsMenu(menu);
254        final Intent i = getIntent();
255        boolean showEnterJid = i != null && i.getBooleanExtra(EXTRA_SHOW_ENTER_JID, false);
256        menu.findItem(R.id.action_scan_qr_code).setVisible(isCameraFeatureAvailable() && showEnterJid);
257        MenuItem mMenuSearchView = menu.findItem(R.id.action_search);
258        if (startSearching) {
259            mMenuSearchView.expandActionView();
260        }
261        return true;
262    }
263
264    @Override
265    public void onSaveInstanceState(Bundle savedInstanceState) {
266        savedInstanceState.putStringArray("selected_contacts", getSelectedContactJids());
267        super.onSaveInstanceState(savedInstanceState);
268    }
269
270    @Override
271    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
272        if (multiple) {
273            return false;
274        } else {
275            List<ListItem> items = getListItems();
276            if (items.size() == 1) {
277                onListItemClicked(items.get(0));
278                return true;
279            }
280            return false;
281        }
282    }
283
284    protected void filterContacts(final String needle) {
285        getListItems().clear();
286        if (xmppConnectionService == null) {
287            getListItemAdapter().notifyDataSetChanged();
288            return;
289        }
290        final var accounts = new ArrayList<Account>();
291        for (final var account : xmppConnectionService.getAccounts()) {
292            if (mActivatedAccounts.contains(account.getJid().asBareJid().toEscapedString())) accounts.add(account);
293        }
294        for (final Account account : accounts) {
295            for (final Contact contact : account.getRoster().getContacts()) {
296                if (contact.showInContactList() &&
297                        !filterContacts.contains(contact.getJid().asBareJid().toString())
298                        && contact.match(this, needle)) {
299                    getListItems().add(contact);
300                }
301            }
302
303            final Contact self = new Contact(account.getSelfContact());
304            self.setSystemName("Note to Self");
305            if (self.match(this, needle)) {
306                getListItems().add(self);
307            }
308        }
309        Collections.sort(getListItems());
310        getListItemAdapter().notifyDataSetChanged();
311        for (int i = 0; i < getListItemAdapter().getCount(); i++) {
312            getListView().setItemChecked(i, selected.contains(getListItemAdapter().getItem(i).getJid().toString()));
313        }
314    }
315
316    private String[] getSelectedContactJids() {
317        return selected.toArray(new String[0]);
318    }
319
320    public void refreshUiReal() {
321        //nothing to do. This Activity doesn't implement any listeners
322    }
323
324    @Override
325    public boolean onOptionsItemSelected(MenuItem item) {
326        switch (item.getItemId()) {
327            case R.id.action_scan_qr_code:
328                ScanActivity.scan(this);
329                return true;
330        }
331        return super.onOptionsItemSelected(item);
332    }
333
334    protected void showEnterJidDialog(XmppUri uri) {
335        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
336        Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog");
337        if (prev != null) {
338            ft.remove(prev);
339        }
340        ft.addToBackStack(null);
341        Jid jid = uri == null ? null : uri.getJid();
342        EnterJidDialog dialog = EnterJidDialog.newInstance(
343                mActivatedAccounts,
344                getString(R.string.enter_contact),
345                getString(R.string.select),
346                null,
347                jid == null ? null : jid.asBareJid().toString(),
348                getIntent().getStringExtra(EXTRA_ACCOUNT),
349                true,
350                false,
351                EnterJidDialog.SanityCheck.NO
352        );
353
354        dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid, x, y) -> {
355            final Intent request = getIntent();
356            final Intent data = new Intent();
357            data.putExtra("contact", contactJid.toString());
358            data.putExtra(EXTRA_ACCOUNT, accountJid.toEscapedString());
359            data.putExtra(EXTRA_SELECT_MULTIPLE, false);
360            copy(request, data);
361            setResult(RESULT_OK, data);
362            finish();
363
364            return true;
365        });
366
367        dialog.show(ft, "dialog");
368    }
369
370    @Override
371    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
372        super.onActivityResult(requestCode, requestCode, intent);
373        ActivityResult activityResult = ActivityResult.of(requestCode, resultCode, intent);
374        if (xmppConnectionService != null) {
375            handleActivityResult(activityResult);
376        } else {
377            this.postponedActivityResult.push(activityResult);
378        }
379    }
380
381    private void handleActivityResult(ActivityResult activityResult) {
382        if (activityResult.resultCode == RESULT_OK && activityResult.requestCode == ScanActivity.REQUEST_SCAN_QR_CODE) {
383            String result = activityResult.data.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
384            XmppUri uri = new XmppUri(Strings.nullToEmpty(result));
385            if (uri.isValidJid()) {
386                showEnterJidDialog(uri);
387            }
388        }
389    }
390
391    @Override
392    protected void onBackendConnected() {
393        this.mActivatedAccounts.clear();
394        final var selected = getIntent().getStringExtra(EXTRA_ACCOUNT);
395        for (final Account account : xmppConnectionService.getAccounts()) {
396            if (account.isEnabled() && (selected == null || selected.equals(account.getJid().asBareJid().toEscapedString()))) {
397                this.mActivatedAccounts.add(account.getJid().asBareJid().toEscapedString());
398            }
399        }
400        filterContacts();
401        ActivityResult activityResult = this.postponedActivityResult.pop();
402        if (activityResult != null) {
403            handleActivityResult(activityResult);
404        }
405        final Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
406        if (fragment instanceof OnBackendConnected) {
407            ((OnBackendConnected) fragment).onBackendConnected();
408        }
409    }
410
411    @Override
412    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
413        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
414        ScanActivity.onRequestPermissionResult(this, requestCode, grantResults);
415    }
416
417    @Override
418    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
419        if (multiple) {
420            if (getListView().isItemChecked(position)) {
421                selected.add(getListItemAdapter().getItem(position).getJid().toString());
422            } else {
423                selected.remove(getListItemAdapter().getItem(position).getJid().toString());
424            }
425
426            if (selected.isEmpty()) {
427                this.binding.fab.setImageResource(R.drawable.ic_person_add_24dp);
428                if (this.showEnterJid) {
429                    this.binding.fab.show();
430                } else {
431                    this.binding.fab.hide();
432                }
433            } else {
434                binding.fab.setImageResource(R.drawable.ic_navigate_next_24dp);
435                binding.fab.show();
436            }
437
438            return;
439        }
440        final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
441        imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
442        final ListItem mListItem = getListItems().get(position);
443        onListItemClicked(mListItem);
444    }
445
446    private void onListItemClicked(ListItem item) {
447        final Intent request = getIntent();
448        final Intent data = new Intent();
449        data.putExtra("contact", item.getJid().toString());
450        String account = request.getStringExtra(EXTRA_ACCOUNT);
451        if (account == null && item instanceof Contact) {
452            account = ((Contact) item).getAccount().getJid().asBareJid().toEscapedString();
453        }
454        data.putExtra(EXTRA_ACCOUNT, account);
455        data.putExtra(EXTRA_SELECT_MULTIPLE, false);
456        copy(request, data);
457        setResult(RESULT_OK, data);
458        finish();
459    }
460}