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.support.annotation.NonNull;
9import android.support.annotation.StringRes;
10import android.support.v4.app.Fragment;
11import android.support.v4.app.FragmentTransaction;
12import android.support.v7.app.ActionBar;
13import android.view.ActionMode;
14import android.view.KeyEvent;
15import android.view.Menu;
16import android.view.MenuItem;
17import android.view.View;
18import android.view.inputmethod.InputMethodManager;
19import android.widget.AbsListView.MultiChoiceModeListener;
20import android.widget.AdapterView;
21import android.widget.ListView;
22import android.widget.TextView;
23
24import java.util.ArrayList;
25import java.util.Arrays;
26import java.util.Collections;
27import java.util.HashSet;
28import java.util.List;
29import java.util.Set;
30
31import eu.siacs.conversations.Config;
32import eu.siacs.conversations.R;
33import eu.siacs.conversations.entities.Account;
34import eu.siacs.conversations.entities.Contact;
35import eu.siacs.conversations.entities.Conversation;
36import eu.siacs.conversations.entities.ListItem;
37import eu.siacs.conversations.entities.MucOptions;
38import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
39import eu.siacs.conversations.ui.util.ActivityResult;
40import eu.siacs.conversations.ui.util.PendingItem;
41import eu.siacs.conversations.utils.XmppUri;
42import eu.siacs.conversations.xmpp.Jid;
43
44public class ChooseContactActivity extends AbstractSearchableListItemActivity 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 List<String> mActivatedAccounts = new ArrayList<>();
52 private 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 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[contacts.size()]));
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_forward_white_24dp);
131 }
132
133 final SharedPreferences preferences = getPreferences();
134 this.startSearching = intent.getBooleanExtra("direct_search", false) && preferences.getBoolean("start_searching", getResources().getBoolean(R.bool.start_searching));
135
136 }
137
138 private void onFabClicked(View v) {
139 if (selected.size() == 0) {
140 showEnterJidDialog(null);
141 } else {
142 submitSelection();
143 }
144 }
145
146 @Override
147 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
148 return false;
149 }
150
151 @Override
152 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
153 mode.setTitle(getTitleFromIntent());
154 binding.fab.setImageResource(R.drawable.ic_forward_white_24dp);
155 binding.fab.show();
156 final View view = getSearchEditText();
157 final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
158 if (view != null && imm != null) {
159 imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
160 }
161 return true;
162 }
163
164 @Override
165 public void onDestroyActionMode(ActionMode mode) {
166 this.binding.fab.setImageResource(R.drawable.ic_person_add_white_24dp);
167 if (this.showEnterJid) {
168 this.binding.fab.show();
169 } else {
170 this.binding.fab.hide();
171 }
172 selected.clear();
173 }
174
175 @Override
176 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
177 return false;
178 }
179
180 private void submitSelection() {
181 final Intent request = getIntent();
182 final Intent data = new Intent();
183 data.putExtra("contacts", getSelectedContactJids());
184 data.putExtra(EXTRA_SELECT_MULTIPLE, true);
185 data.putExtra(EXTRA_ACCOUNT, request.getStringExtra(EXTRA_ACCOUNT));
186 copy(request, data);
187 setResult(RESULT_OK, data);
188 finish();
189 }
190
191 private static void copy(Intent from, Intent to) {
192 to.putExtra(EXTRA_CONVERSATION, from.getStringExtra(EXTRA_CONVERSATION));
193 to.putExtra(EXTRA_GROUP_CHAT_NAME, from.getStringExtra(EXTRA_GROUP_CHAT_NAME));
194 }
195
196 @Override
197 public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
198 if (selected.size() != 0) {
199 getListView().playSoundEffect(0);
200 }
201 Contact item = (Contact) getListItems().get(position);
202 if (checked) {
203 selected.add(item.getJid().toString());
204 } else {
205 selected.remove(item.getJid().toString());
206 }
207 }
208
209 @Override
210 public void onStart() {
211 super.onStart();
212 ActionBar bar = getSupportActionBar();
213 if (bar != null) {
214 try {
215 bar.setTitle(getTitleFromIntent());
216 } catch (Exception e) {
217 bar.setTitle(R.string.title_activity_choose_contact);
218 }
219 }
220 }
221
222 public @StringRes
223 int getTitleFromIntent() {
224 final Intent intent = getIntent();
225 boolean multiple = intent != null && intent.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false);
226 @StringRes int fallback = multiple ? R.string.title_activity_choose_contacts : R.string.title_activity_choose_contact;
227 return intent != null ? intent.getIntExtra(EXTRA_TITLE_RES_ID, fallback) : fallback;
228 }
229
230 @Override
231 public boolean onCreateOptionsMenu(final Menu menu) {
232 super.onCreateOptionsMenu(menu);
233 final Intent i = getIntent();
234 boolean showEnterJid = i != null && i.getBooleanExtra(EXTRA_SHOW_ENTER_JID, false);
235 menu.findItem(R.id.action_scan_qr_code).setVisible(isCameraFeatureAvailable() && showEnterJid);
236 MenuItem mMenuSearchView = menu.findItem(R.id.action_search);
237 if (startSearching) {
238 mMenuSearchView.expandActionView();
239 }
240 return true;
241 }
242
243 @Override
244 public void onSaveInstanceState(Bundle savedInstanceState) {
245 savedInstanceState.putStringArray("selected_contacts", getSelectedContactJids());
246 super.onSaveInstanceState(savedInstanceState);
247 }
248
249 @Override
250 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
251 if (multiple) {
252 return false;
253 } else {
254 List<ListItem> items = getListItems();
255 if (items.size() == 1) {
256 onListItemClicked(items.get(0));
257 return true;
258 }
259 return false;
260 }
261 }
262
263 protected void filterContacts(final String needle) {
264 getListItems().clear();
265 if (xmppConnectionService == null) {
266 getListItemAdapter().notifyDataSetChanged();
267 return;
268 }
269 for (final Account account : xmppConnectionService.getAccounts()) {
270 if (account.getStatus() != Account.State.DISABLED) {
271 for (final Contact contact : account.getRoster().getContacts()) {
272 if (contact.showInContactList() &&
273 !filterContacts.contains(contact.getJid().asBareJid().toString())
274 && contact.match(this, needle)) {
275 getListItems().add(contact);
276 }
277 }
278 }
279 }
280 Collections.sort(getListItems());
281 getListItemAdapter().notifyDataSetChanged();
282 }
283
284 private String[] getSelectedContactJids() {
285 return selected.toArray(new String[0]);
286 }
287
288 public void refreshUiReal() {
289 //nothing to do. This Activity doesn't implement any listeners
290 }
291
292 @Override
293 public boolean onOptionsItemSelected(MenuItem item) {
294 switch (item.getItemId()) {
295 case R.id.action_scan_qr_code:
296 ScanActivity.scan(this);
297 return true;
298 }
299 return super.onOptionsItemSelected(item);
300 }
301
302 protected void showEnterJidDialog(XmppUri uri) {
303 FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
304 Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog");
305 if (prev != null) {
306 ft.remove(prev);
307 }
308 ft.addToBackStack(null);
309 Jid jid = uri == null ? null : uri.getJid();
310 EnterJidDialog dialog = EnterJidDialog.newInstance(
311 mActivatedAccounts,
312 getString(R.string.enter_contact),
313 getString(R.string.select),
314 jid == null ? null : jid.asBareJid().toString(),
315 getIntent().getStringExtra(EXTRA_ACCOUNT),
316 true,
317 false
318 );
319
320 dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> {
321 final Intent request = getIntent();
322 final Intent data = new Intent();
323 data.putExtra("contact", contactJid.toString());
324 data.putExtra(EXTRA_ACCOUNT, accountJid.toString());
325 data.putExtra(EXTRA_SELECT_MULTIPLE, false);
326 copy(request, data);
327 setResult(RESULT_OK, data);
328 finish();
329
330 return true;
331 });
332
333 dialog.show(ft, "dialog");
334 }
335
336 @Override
337 public void onActivityResult(int requestCode, int resultCode, Intent intent) {
338 super.onActivityResult(requestCode, requestCode, intent);
339 ActivityResult activityResult = ActivityResult.of(requestCode, resultCode, intent);
340 if (xmppConnectionService != null) {
341 handleActivityResult(activityResult);
342 } else {
343 this.postponedActivityResult.push(activityResult);
344 }
345 }
346
347 private void handleActivityResult(ActivityResult activityResult) {
348 if (activityResult.resultCode == RESULT_OK && activityResult.requestCode == ScanActivity.REQUEST_SCAN_QR_CODE) {
349 String result = activityResult.data.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
350 XmppUri uri = new XmppUri(result == null ? "" : result);
351 if (uri.isValidJid()) {
352 showEnterJidDialog(uri);
353 }
354 }
355 }
356
357 @Override
358 void onBackendConnected() {
359 filterContacts();
360 this.mActivatedAccounts.clear();
361 for (Account account : xmppConnectionService.getAccounts()) {
362 if (account.getStatus() != Account.State.DISABLED) {
363 if (Config.DOMAIN_LOCK != null) {
364 this.mActivatedAccounts.add(account.getJid().getEscapedLocal());
365 } else {
366 this.mActivatedAccounts.add(account.getJid().asBareJid().toEscapedString());
367 }
368 }
369 }
370 ActivityResult activityResult = this.postponedActivityResult.pop();
371 if (activityResult != null) {
372 handleActivityResult(activityResult);
373 }
374 final Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
375 if (fragment instanceof OnBackendConnected) {
376 ((OnBackendConnected) fragment).onBackendConnected();
377 }
378 }
379
380 @Override
381 public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
382 ScanActivity.onRequestPermissionResult(this, requestCode, grantResults);
383 }
384
385 @Override
386 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
387 if (multiple) {
388 startActionMode(this);
389 getListView().setItemChecked(position, true);
390 return;
391 }
392 final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
393 imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
394 final ListItem mListItem = getListItems().get(position);
395 onListItemClicked(mListItem);
396 }
397
398 private void onListItemClicked(ListItem item) {
399 final Intent request = getIntent();
400 final Intent data = new Intent();
401 data.putExtra("contact", item.getJid().toString());
402 String account = request.getStringExtra(EXTRA_ACCOUNT);
403 if (account == null && item instanceof Contact) {
404 account = ((Contact) item).getAccount().getJid().asBareJid().toString();
405 }
406 data.putExtra(EXTRA_ACCOUNT, account);
407 data.putExtra(EXTRA_SELECT_MULTIPLE, false);
408 copy(request, data);
409 setResult(RESULT_OK, data);
410 finish();
411 }
412}