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 onPrepareActionMode(ActionMode mode, Menu menu) {
161        return false;
162    }
163
164    @Override
165    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
166        mode.setTitle(getTitleFromIntent());
167        binding.chooseContactList.setFastScrollEnabled(false);
168        binding.fab.setImageResource(R.drawable.ic_navigate_next_24dp);
169        binding.fab.show();
170        final View view = getSearchEditText();
171        final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
172        if (view != null && imm != null) {
173            imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
174        }
175        return true;
176    }
177
178    @Override
179    public void onDestroyActionMode(ActionMode mode) {
180        this.binding.fab.setImageResource(R.drawable.ic_person_add_24dp);
181        if (this.showEnterJid) {
182            this.binding.fab.show();
183        } else {
184            this.binding.fab.hide();
185        }
186        binding.chooseContactList.setFastScrollEnabled(true);
187        selected.clear();
188    }
189
190    @Override
191    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
192        return false;
193    }
194
195    private void submitSelection() {
196        final Intent request = getIntent();
197        final Intent data = new Intent();
198        data.putExtra("contacts", getSelectedContactJids());
199        data.putExtra(EXTRA_SELECT_MULTIPLE, true);
200        data.putExtra(EXTRA_ACCOUNT, request.getStringExtra(EXTRA_ACCOUNT));
201        copy(request, data);
202        setResult(RESULT_OK, data);
203        finish();
204    }
205
206    private static void copy(Intent from, Intent to) {
207        to.putExtra(EXTRA_CONVERSATION, from.getStringExtra(EXTRA_CONVERSATION));
208        to.putExtra(EXTRA_GROUP_CHAT_NAME, from.getStringExtra(EXTRA_GROUP_CHAT_NAME));
209    }
210
211    @Override
212    public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
213        if (selected.size() != 0) {
214            getListView().playSoundEffect(SoundEffectConstants.CLICK);
215        }
216        getListItemAdapter().notifyDataSetChanged();
217        Contact item = (Contact) getListItems().get(position);
218        if (checked) {
219            selected.add(item.getJid().toString());
220        } else {
221            selected.remove(item.getJid().toString());
222        }
223    }
224
225    @Override
226    public void onStart() {
227        super.onStart();
228        ActionBar bar = getSupportActionBar();
229        if (bar != null) {
230            try {
231                bar.setTitle(getTitleFromIntent());
232            } catch (Exception e) {
233                bar.setTitle(R.string.title_activity_choose_contact);
234            }
235        }
236    }
237
238    public @StringRes
239    int getTitleFromIntent() {
240        final Intent intent = getIntent();
241        boolean multiple = intent != null && intent.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false);
242        @StringRes int fallback = multiple ? R.string.title_activity_choose_contacts : R.string.title_activity_choose_contact;
243        return intent != null ? intent.getIntExtra(EXTRA_TITLE_RES_ID, fallback) : fallback;
244    }
245
246    @Override
247    public boolean onCreateOptionsMenu(final Menu menu) {
248        super.onCreateOptionsMenu(menu);
249        final Intent i = getIntent();
250        boolean showEnterJid = i != null && i.getBooleanExtra(EXTRA_SHOW_ENTER_JID, false);
251        menu.findItem(R.id.action_scan_qr_code).setVisible(isCameraFeatureAvailable() && showEnterJid);
252        MenuItem mMenuSearchView = menu.findItem(R.id.action_search);
253        if (startSearching) {
254            mMenuSearchView.expandActionView();
255        }
256        return true;
257    }
258
259    @Override
260    public void onSaveInstanceState(Bundle savedInstanceState) {
261        savedInstanceState.putStringArray("selected_contacts", getSelectedContactJids());
262        super.onSaveInstanceState(savedInstanceState);
263    }
264
265    @Override
266    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
267        if (multiple) {
268            return false;
269        } else {
270            List<ListItem> items = getListItems();
271            if (items.size() == 1) {
272                onListItemClicked(items.get(0));
273                return true;
274            }
275            return false;
276        }
277    }
278
279    protected void filterContacts(final String needle) {
280        getListItems().clear();
281        if (xmppConnectionService == null) {
282            getListItemAdapter().notifyDataSetChanged();
283            return;
284        }
285        for (final Account account : xmppConnectionService.getAccounts()) {
286            if (account.isEnabled()) {
287                for (final Contact contact : account.getRoster().getContacts()) {
288                    if (contact.showInContactList() &&
289                            !filterContacts.contains(contact.getJid().asBareJid().toString())
290                            && contact.match(this, needle)) {
291                        getListItems().add(contact);
292                    }
293                }
294
295                final Contact self = new Contact(account.getSelfContact());
296                self.setSystemName("Note to Self");
297                if (self.match(this, needle)) {
298                    getListItems().add(self);
299                }
300            }
301        }
302        Collections.sort(getListItems());
303        getListItemAdapter().notifyDataSetChanged();
304        for (int i = 0; i < getListItemAdapter().getCount(); i++) {
305            getListView().setItemChecked(i, selected.contains(getListItemAdapter().getItem(i).getJid().toString()));
306        }
307    }
308
309    private String[] getSelectedContactJids() {
310        return selected.toArray(new String[0]);
311    }
312
313    public void refreshUiReal() {
314        //nothing to do. This Activity doesn't implement any listeners
315    }
316
317    @Override
318    public boolean onOptionsItemSelected(MenuItem item) {
319        switch (item.getItemId()) {
320            case R.id.action_scan_qr_code:
321                ScanActivity.scan(this);
322                return true;
323        }
324        return super.onOptionsItemSelected(item);
325    }
326
327    protected void showEnterJidDialog(XmppUri uri) {
328        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
329        Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog");
330        if (prev != null) {
331            ft.remove(prev);
332        }
333        ft.addToBackStack(null);
334        Jid jid = uri == null ? null : uri.getJid();
335        EnterJidDialog dialog = EnterJidDialog.newInstance(
336                mActivatedAccounts,
337                getString(R.string.enter_contact),
338                getString(R.string.select),
339                null,
340                jid == null ? null : jid.asBareJid().toString(),
341                getIntent().getStringExtra(EXTRA_ACCOUNT),
342                true,
343                false,
344                EnterJidDialog.SanityCheck.NO
345        );
346
347        dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid, x, y) -> {
348            final Intent request = getIntent();
349            final Intent data = new Intent();
350            data.putExtra("contact", contactJid.toString());
351            data.putExtra(EXTRA_ACCOUNT, accountJid.toEscapedString());
352            data.putExtra(EXTRA_SELECT_MULTIPLE, false);
353            copy(request, data);
354            setResult(RESULT_OK, data);
355            finish();
356
357            return true;
358        });
359
360        dialog.show(ft, "dialog");
361    }
362
363    @Override
364    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
365        super.onActivityResult(requestCode, requestCode, intent);
366        ActivityResult activityResult = ActivityResult.of(requestCode, resultCode, intent);
367        if (xmppConnectionService != null) {
368            handleActivityResult(activityResult);
369        } else {
370            this.postponedActivityResult.push(activityResult);
371        }
372    }
373
374    private void handleActivityResult(ActivityResult activityResult) {
375        if (activityResult.resultCode == RESULT_OK && activityResult.requestCode == ScanActivity.REQUEST_SCAN_QR_CODE) {
376            String result = activityResult.data.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
377            XmppUri uri = new XmppUri(Strings.nullToEmpty(result));
378            if (uri.isValidJid()) {
379                showEnterJidDialog(uri);
380            }
381        }
382    }
383
384    @Override
385    protected void onBackendConnected() {
386        filterContacts();
387        this.mActivatedAccounts.clear();
388        for (final Account account : xmppConnectionService.getAccounts()) {
389            if (account.isEnabled()) {
390                this.mActivatedAccounts.add(account.getJid().asBareJid().toEscapedString());
391            }
392        }
393        ActivityResult activityResult = this.postponedActivityResult.pop();
394        if (activityResult != null) {
395            handleActivityResult(activityResult);
396        }
397        final Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
398        if (fragment instanceof OnBackendConnected) {
399            ((OnBackendConnected) fragment).onBackendConnected();
400        }
401    }
402
403    @Override
404    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
405        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
406        ScanActivity.onRequestPermissionResult(this, requestCode, grantResults);
407    }
408
409    @Override
410    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
411        if (multiple) {
412            if (getListView().isItemChecked(position)) {
413                selected.add(getListItemAdapter().getItem(position).getJid().toString());
414            } else {
415                selected.remove(getListItemAdapter().getItem(position).getJid().toString());
416            }
417
418            if (selected.isEmpty()) {
419                this.binding.fab.setImageResource(R.drawable.ic_person_add_24dp);
420                if (this.showEnterJid) {
421                    this.binding.fab.show();
422                } else {
423                    this.binding.fab.hide();
424                }
425            } else {
426                binding.fab.setImageResource(R.drawable.ic_navigate_next_24dp);
427                binding.fab.show();
428            }
429
430            return;
431        }
432        final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
433        imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
434        final ListItem mListItem = getListItems().get(position);
435        onListItemClicked(mListItem);
436    }
437
438    private void onListItemClicked(ListItem item) {
439        final Intent request = getIntent();
440        final Intent data = new Intent();
441        data.putExtra("contact", item.getJid().toString());
442        String account = request.getStringExtra(EXTRA_ACCOUNT);
443        if (account == null && item instanceof Contact) {
444            account = ((Contact) item).getAccount().getJid().asBareJid().toEscapedString();
445        }
446        data.putExtra(EXTRA_ACCOUNT, account);
447        data.putExtra(EXTRA_SELECT_MULTIPLE, false);
448        copy(request, data);
449        setResult(RESULT_OK, data);
450        finish();
451    }
452}