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}