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