1package eu.siacs.conversations.ui;
2
3import java.io.UnsupportedEncodingException;
4import java.net.URLDecoder;
5import java.util.ArrayList;
6import java.util.Collections;
7import java.util.Comparator;
8import java.util.List;
9
10import eu.siacs.conversations.R;
11import eu.siacs.conversations.entities.Account;
12import eu.siacs.conversations.entities.Contact;
13import eu.siacs.conversations.entities.Conversation;
14import eu.siacs.conversations.utils.CryptoHelper;
15import eu.siacs.conversations.utils.UIHelper;
16import eu.siacs.conversations.utils.Validator;
17import android.os.Bundle;
18import android.preference.PreferenceManager;
19import android.text.Editable;
20import android.text.TextWatcher;
21import android.util.SparseBooleanArray;
22import android.view.ActionMode;
23import android.view.LayoutInflater;
24import android.view.Menu;
25import android.view.MenuInflater;
26import android.view.MenuItem;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.AbsListView;
30import android.widget.AdapterView;
31import android.widget.AdapterView.OnItemClickListener;
32import android.widget.AdapterView.OnItemLongClickListener;
33import android.widget.ArrayAdapter;
34import android.widget.EditText;
35import android.widget.ListView;
36import android.widget.TextView;
37import android.widget.ImageView;
38import android.widget.Toast;
39import android.annotation.SuppressLint;
40import android.app.AlertDialog;
41import android.content.Context;
42import android.content.DialogInterface;
43import android.content.SharedPreferences;
44import android.content.DialogInterface.OnClickListener;
45import android.content.Intent;
46
47public class ContactsActivity extends XmppActivity {
48
49 protected List<Contact> rosterContacts = new ArrayList<Contact>();
50 protected List<Contact> aggregatedContacts = new ArrayList<Contact>();
51 protected ListView contactsView;
52 protected ArrayAdapter<Contact> contactsAdapter;
53
54 protected EditText search;
55 protected String searchString = "";
56 private TextView contactsHeader;
57 private List<Account> accounts;
58 private List<Contact> selectedContacts = new ArrayList<Contact>();
59
60 private ContactsActivity activity = this;
61
62 private boolean useSubject = true;
63 private boolean isActionMode = false;
64 private boolean inviteIntent = false;
65 private ActionMode actionMode = null;
66 private AbsListView.MultiChoiceModeListener actionModeCallback = new AbsListView.MultiChoiceModeListener() {
67
68 @Override
69 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
70 menu.clear();
71 MenuInflater inflater = mode.getMenuInflater();
72 inflater.inflate(R.menu.newconversation_context, menu);
73 SparseBooleanArray checkedItems = contactsView
74 .getCheckedItemPositions();
75 selectedContacts.clear();
76 for (int i = 0; i < aggregatedContacts.size(); ++i) {
77 if (checkedItems.get(i, false)) {
78 selectedContacts.add(aggregatedContacts.get(i));
79 }
80 }
81 if (selectedContacts.size() == 0) {
82 menu.findItem(R.id.action_start_conversation).setVisible(false);
83 menu.findItem(R.id.action_contact_details).setVisible(false);
84 menu.findItem(R.id.action_invite).setVisible(false);
85 menu.findItem(R.id.action_invite_to_existing).setVisible(false);
86 } else if ((selectedContacts.size() == 1) && (!inviteIntent)) {
87 menu.findItem(R.id.action_start_conversation).setVisible(true);
88 menu.findItem(R.id.action_contact_details).setVisible(true);
89 menu.findItem(R.id.action_invite).setVisible(false);
90 menu.findItem(R.id.action_invite_to_existing).setVisible(true);
91 } else if (!inviteIntent) {
92 menu.findItem(R.id.action_start_conversation).setVisible(true);
93 menu.findItem(R.id.action_contact_details).setVisible(false);
94 menu.findItem(R.id.action_invite).setVisible(false);
95 menu.findItem(R.id.action_invite_to_existing).setVisible(true);
96 } else {
97 menu.findItem(R.id.action_invite).setVisible(true);
98 menu.findItem(R.id.action_start_conversation).setVisible(false);
99 menu.findItem(R.id.action_contact_details).setVisible(false);
100 menu.findItem(R.id.action_invite_to_existing).setVisible(false);
101 }
102 return true;
103 }
104
105 @Override
106 public void onDestroyActionMode(ActionMode mode) {
107 // TODO Auto-generated method stub
108
109 }
110
111 @Override
112 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
113 return true;
114 }
115
116 @Override
117 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
118 switch (item.getItemId()) {
119 case R.id.action_start_conversation:
120 if (selectedContacts.size() == 1) {
121 startConversation(selectedContacts.get(0));
122 } else {
123 startConference();
124 }
125 break;
126 case R.id.action_contact_details:
127 Intent intent = new Intent(getApplicationContext(),
128 ContactDetailsActivity.class);
129 intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
130 intent.putExtra("account", selectedContacts.get(0).getAccount().getJid());
131 intent.putExtra("contact",selectedContacts.get(0).getJid());
132 startActivity(intent);
133 finish();
134 break;
135 case R.id.action_invite:
136 invite();
137 break;
138 case R.id.action_invite_to_existing:
139 final List<Conversation> mucs = new ArrayList<Conversation>();
140 for(Conversation conv : xmppConnectionService.getConversations()) {
141 if (conv.getMode() == Conversation.MODE_MULTI) {
142 mucs.add(conv);
143 }
144 }
145 AlertDialog.Builder builder = new AlertDialog.Builder(activity);
146 builder.setTitle(getString(R.string.invite_contacts_to_existing));
147 if (mucs.size() >= 1) {
148 String[] options = new String[mucs.size()];
149 for(int i = 0; i < options.length; ++i) {
150 options[i] = mucs.get(i).getName(useSubject);
151 }
152 builder.setItems(options, new OnClickListener() {
153
154 @Override
155 public void onClick(DialogInterface dialog, int which) {
156 Conversation conversation = mucs.get(which);
157 if (isOnline(conversation.getAccount())) {
158 xmppConnectionService.inviteToConference(conversation, selectedContacts);
159 Toast.makeText(activity, getString(R.string.invitation_sent), Toast.LENGTH_SHORT).show();
160 actionMode.finish();
161 }
162 }
163 });
164 } else {
165 builder.setMessage(getString(R.string.no_open_mucs));
166 }
167 builder.setNegativeButton(getString(R.string.cancel),null);
168 builder.create().show();
169 break;
170 default:
171 break;
172 }
173 return false;
174 }
175
176 @Override
177 public void onItemCheckedStateChanged(ActionMode mode, int position,
178 long id, boolean checked) {
179 }
180 };
181
182 private boolean isOnline(Account account) {
183 if (account.getStatus() == Account.STATUS_ONLINE) {
184 return true;
185 } else {
186 AlertDialog.Builder builder = new AlertDialog.Builder(this);
187 builder.setTitle(getString(R.string.account_offline));
188 builder.setMessage(getString(R.string.cant_invite_while_offline));
189 builder.setNegativeButton(getString(R.string.ok), null);
190 builder.setIconAttribute(android.R.attr.alertDialogIcon);
191 builder.create().show();
192 return false;
193 }
194 }
195
196 private void invite() {
197 List<Conversation> conversations = xmppConnectionService
198 .getConversations();
199 Conversation conversation = null;
200 for (Conversation tmpConversation : conversations) {
201 if (tmpConversation.getUuid().equals(
202 getIntent().getStringExtra("uuid"))) {
203 conversation = tmpConversation;
204 break;
205 }
206 }
207 if (conversation != null) {
208 xmppConnectionService.inviteToConference(conversation,
209 selectedContacts);
210 }
211 finish();
212 }
213
214 private void startConference() {
215 if (accounts.size() > 1) {
216 getAccountChooser(new OnClickListener() {
217
218 @Override
219 public void onClick(DialogInterface dialog, int which) {
220 startConference(accounts.get(which));
221 }
222 }).show();
223 } else {
224 startConference(accounts.get(0));
225 }
226
227 }
228
229 private void startConference(final Account account) {
230 if (isOnline(account)) {
231 AlertDialog.Builder builder = new AlertDialog.Builder(this);
232 builder.setTitle(getString(R.string.new_conference));
233 builder.setMessage(getString(R.string.new_conference_explained));
234 builder.setNegativeButton(getString(R.string.cancel), null);
235 builder.setPositiveButton(getString(R.string.create_invite),
236 new OnClickListener() {
237
238 @Override
239 public void onClick(DialogInterface dialog, int which) {
240 String mucName = CryptoHelper.randomMucName(xmppConnectionService.getRNG());
241 String serverName = account.getXmppConnection()
242 .getMucServer();
243 if (serverName==null) {
244 List<String> servers = getMucServers();
245 if (servers.size() >= 1) {
246 serverName = servers.get(0);
247 } else {
248 displayErrorDialog(R.string.no_muc_server_found);
249 return;
250 }
251 }
252 String jid = mucName + "@" + serverName;
253 Conversation conversation = xmppConnectionService
254 .findOrCreateConversation(account, jid, true);
255 StringBuilder subject = new StringBuilder();
256 subject.append(account.getUsername() + ", ");
257 for (int i = 0; i < selectedContacts.size(); ++i) {
258 if (i + 1 != selectedContacts.size()) {
259 subject.append(selectedContacts.get(i)
260 .getDisplayName() + ", ");
261 } else {
262 subject.append(selectedContacts.get(i)
263 .getDisplayName());
264 }
265 }
266 xmppConnectionService.sendConversationSubject(
267 conversation, subject.toString());
268 xmppConnectionService.inviteToConference(conversation,
269 selectedContacts);
270 switchToConversation(conversation, null,false);
271 }
272 });
273 builder.create().show();
274 }
275 }
276
277 protected void updateAggregatedContacts() {
278
279 aggregatedContacts.clear();
280 for (Contact contact : rosterContacts) {
281 if (contact.match(searchString)&&(contact.showInRoster()))
282 aggregatedContacts.add(contact);
283 }
284
285 Collections.sort(aggregatedContacts, new Comparator<Contact>() {
286
287 @SuppressLint("DefaultLocale")
288 @Override
289 public int compare(Contact lhs, Contact rhs) {
290 return lhs.getDisplayName().toLowerCase()
291 .compareTo(rhs.getDisplayName().toLowerCase());
292 }
293 });
294
295 if (aggregatedContacts.size() == 0) {
296
297 if (Validator.isValidJid(searchString)) {
298 Contact newContact = new Contact(searchString);
299 newContact.resetOption(Contact.Options.IN_ROSTER);
300 aggregatedContacts.add(newContact);
301 contactsHeader.setText(getString(R.string.new_contact));
302 } else {
303 contactsHeader.setText(getString(R.string.contacts));
304 }
305 } else {
306 contactsHeader.setText(getString(R.string.contacts));
307 }
308
309 contactsAdapter.notifyDataSetChanged();
310 contactsView.setScrollX(0);
311 }
312
313 private OnItemLongClickListener onLongClickListener = new OnItemLongClickListener() {
314
315 @Override
316 public boolean onItemLongClick(AdapterView<?> arg0, View view,
317 int position, long arg3) {
318 if (!isActionMode) {
319 contactsView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
320 contactsView.setItemChecked(position, true);
321 actionMode = contactsView.startActionMode(actionModeCallback);
322 }
323 return true;
324 }
325 };
326
327 @Override
328 protected void onStart() {
329 super.onStart();
330 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
331 this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
332 inviteIntent = "invite".equals(getIntent().getAction());
333 if (inviteIntent) {
334 contactsHeader.setVisibility(View.GONE);
335 actionMode = contactsView.startActionMode(actionModeCallback);
336 search.setVisibility(View.GONE);
337 }
338 }
339
340 @Override
341 protected void onCreate(Bundle savedInstanceState) {
342
343 super.onCreate(savedInstanceState);
344
345 setContentView(R.layout.activity_new_conversation);
346
347 contactsHeader = (TextView) findViewById(R.id.contacts_header);
348
349 search = (EditText) findViewById(R.id.new_conversation_search);
350 search.addTextChangedListener(new TextWatcher() {
351
352 @Override
353 public void onTextChanged(CharSequence s, int start, int before,
354 int count) {
355 searchString = search.getText().toString();
356 updateAggregatedContacts();
357 }
358
359 @Override
360 public void afterTextChanged(Editable s) {
361 // TODO Auto-generated method stub
362
363 }
364
365 @Override
366 public void beforeTextChanged(CharSequence s, int start, int count,
367 int after) {
368 // TODO Auto-generated method stub
369
370 }
371 });
372
373 contactsView = (ListView) findViewById(R.id.contactList);
374 contactsAdapter = new ArrayAdapter<Contact>(getApplicationContext(),
375 R.layout.contact, aggregatedContacts) {
376 @Override
377 public View getView(int position, View view, ViewGroup parent) {
378 LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
379 Contact contact = getItem(position);
380 if (view == null) {
381 view = (View) inflater.inflate(R.layout.contact, null);
382 }
383
384 ((TextView) view.findViewById(R.id.contact_display_name))
385 .setText(getItem(position).getDisplayName());
386 TextView contactJid = (TextView) view
387 .findViewById(R.id.contact_jid);
388 contactJid.setText(contact.getJid());
389 ImageView imageView = (ImageView) view
390 .findViewById(R.id.contact_photo);
391 imageView.setImageBitmap(UIHelper.getContactPicture(contact, 48, this.getContext(), false));
392 return view;
393 }
394 };
395 contactsView.setAdapter(contactsAdapter);
396 contactsView.setMultiChoiceModeListener(actionModeCallback);
397 contactsView.setOnItemClickListener(new OnItemClickListener() {
398
399 @Override
400 public void onItemClick(AdapterView<?> arg0, final View view,
401 int pos, long arg3) {
402 if (!isActionMode) {
403 Contact clickedContact = aggregatedContacts.get(pos);
404 startConversation(clickedContact);
405
406 } else {
407 actionMode.invalidate();
408 }
409 }
410 });
411 contactsView.setOnItemLongClickListener(this.onLongClickListener);
412 }
413
414 public void startConversation(final Contact contact) {
415 if ((contact.getAccount() == null) && (accounts.size() > 1)) {
416 getAccountChooser(new OnClickListener() {
417
418 @Override
419 public void onClick(DialogInterface dialog, int which) {
420 contact.setAccount(accounts.get(which));
421 showIsMucDialogIfNeeded(contact);
422 }
423 }).show();
424 } else {
425 if (contact.getAccount() == null) {
426 contact.setAccount(accounts.get(0));
427 }
428 showIsMucDialogIfNeeded(contact);
429 }
430 }
431
432 protected AlertDialog getAccountChooser(OnClickListener listener) {
433 String[] accountList = new String[accounts.size()];
434 for (int i = 0; i < accounts.size(); ++i) {
435 accountList[i] = accounts.get(i).getJid();
436 }
437
438 AlertDialog.Builder accountChooser = new AlertDialog.Builder(this);
439 accountChooser.setTitle(getString(R.string.choose_account));
440 accountChooser.setItems(accountList, listener);
441 return accountChooser.create();
442 }
443
444 public void showIsMucDialogIfNeeded(final Contact clickedContact) {
445 if (isMuc(clickedContact)) {
446 startConversation(clickedContact,clickedContact.getAccount(), true);
447 } else if (clickedContact.couldBeMuc()) {
448 AlertDialog.Builder dialog = new AlertDialog.Builder(this);
449 dialog.setTitle(getString(R.string.multi_user_conference));
450 dialog.setMessage(getString(R.string.trying_join_conference));
451 dialog.setPositiveButton(getString(R.string.yes), new OnClickListener() {
452
453 @Override
454 public void onClick(DialogInterface dialog, int which) {
455 startConversation(clickedContact,
456 clickedContact.getAccount(), true);
457 }
458 });
459 dialog.setNegativeButton(getString(R.string.no), new OnClickListener() {
460
461 @Override
462 public void onClick(DialogInterface dialog, int which) {
463 startConversation(clickedContact,
464 clickedContact.getAccount(), false);
465 }
466 });
467 dialog.create().show();
468 } else {
469 startConversation(clickedContact, clickedContact.getAccount(),
470 false);
471 }
472 }
473
474 private List<String> getMucServers() {
475 ArrayList<String> mucServers = new ArrayList<String>();
476 for(Account account : accounts) {
477 if (account.getXmppConnection()!=null) {
478 String server = account.getXmppConnection().getMucServer();
479 if (server!=null) {
480 mucServers.add(server);
481 }
482 }
483 }
484 return mucServers;
485 }
486
487 private boolean isMuc(Contact contact) {
488 String[] parts = contact.getJid().split("@");
489 if (parts.length != 2) {
490 return false;
491 }
492 return getMucServers().contains(parts[1]);
493 }
494
495 public void startConversation(Contact contact, Account account, boolean muc) {
496 if (!contact.getOption(Contact.Options.IN_ROSTER)&&(!muc)) {
497 xmppConnectionService.createContact(contact);
498 }
499 Conversation conversation = xmppConnectionService
500 .findOrCreateConversation(account, contact.getJid(), muc);
501
502 switchToConversation(conversation, null,false);
503 }
504
505 @Override
506 void onBackendConnected() {
507 this.accounts = xmppConnectionService.getAccounts();
508 if (Intent.ACTION_SENDTO.equals(getIntent().getAction())) {
509 getActionBar().setDisplayHomeAsUpEnabled(false);
510 getActionBar().setHomeButtonEnabled(false);
511 String jid;
512 try {
513 jid = URLDecoder.decode(getIntent().getData().getEncodedPath(),
514 "UTF-8").split("/")[1];
515 } catch (UnsupportedEncodingException e) {
516 jid = null;
517 }
518 if (jid != null) {
519 final String finalJid = jid;
520 if (this.accounts.size() > 1) {
521 getAccountChooser(new OnClickListener() {
522
523 @Override
524 public void onClick(DialogInterface dialog, int which) {
525 Conversation conversation = xmppConnectionService
526 .findOrCreateConversation(
527 accounts.get(which), finalJid,
528 false);
529 switchToConversation(conversation, null,false);
530 finish();
531 }
532 }).show();
533 } else {
534 Conversation conversation = xmppConnectionService
535 .findOrCreateConversation(this.accounts.get(0),
536 jid, false);
537 switchToConversation(conversation, null,false);
538 finish();
539 }
540 }
541 }
542
543 if (xmppConnectionService.getConversationCount() == 0) {
544 getActionBar().setDisplayHomeAsUpEnabled(false);
545 getActionBar().setHomeButtonEnabled(false);
546 }
547 this.rosterContacts.clear();
548 for(Account account : accounts) {
549 if (account.getStatus() != Account.STATUS_DISABLED) {
550 rosterContacts.addAll(account.getRoster().getContacts());
551 }
552 }
553 updateAggregatedContacts();
554 }
555
556 @Override
557 public boolean onCreateOptionsMenu(Menu menu) {
558 // Inflate the menu; this adds items to the action bar if it is present.
559 getMenuInflater().inflate(R.menu.newconversation, menu);
560 return true;
561 }
562
563 @Override
564 public boolean onOptionsItemSelected(MenuItem item) {
565 switch (item.getItemId()) {
566 default:
567 break;
568 }
569 return super.onOptionsItemSelected(item);
570 }
571
572 @Override
573 public void onActionModeStarted(ActionMode mode) {
574 super.onActionModeStarted(mode);
575 this.isActionMode = true;
576 search.setEnabled(false);
577 }
578
579 @Override
580 public void onActionModeFinished(ActionMode mode) {
581 super.onActionModeFinished(mode);
582 if (inviteIntent) {
583 finish();
584 } else {
585 this.isActionMode = false;
586 contactsView.clearChoices();
587 contactsView.requestLayout();
588 contactsView.post(new Runnable() {
589 @Override
590 public void run() {
591 contactsView.setChoiceMode(ListView.CHOICE_MODE_NONE);
592 }
593 });
594 search.setEnabled(true);
595 }
596 }
597
598}