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