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.TextView;
38import android.widget.ImageView;
39import android.widget.Toast;
40import android.annotation.SuppressLint;
41import android.app.AlertDialog;
42import android.content.Context;
43import android.content.DialogInterface;
44import android.content.SharedPreferences;
45import android.content.DialogInterface.OnClickListener;
46import android.content.Intent;
47
48public class ContactsActivity extends XmppActivity {
49
50 protected List<Contact> rosterContacts = new ArrayList<Contact>();
51 protected List<Contact> aggregatedContacts = new ArrayList<Contact>();
52 protected ListView contactsView;
53 protected ArrayAdapter<Contact> contactsAdapter;
54
55 protected EditText search;
56 protected String searchString = "";
57 private TextView contactsHeader;
58 private List<Account> accounts;
59 private List<Contact> selectedContacts = new ArrayList<Contact>();
60
61 private ContactsActivity activity = this;
62
63 private boolean useSubject = true;
64 private boolean isActionMode = false;
65 private boolean inviteIntent = false;
66 private ActionMode actionMode = null;
67 private AbsListView.MultiChoiceModeListener actionModeCallback = new AbsListView.MultiChoiceModeListener() {
68
69 @Override
70 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
71 menu.clear();
72 MenuInflater inflater = mode.getMenuInflater();
73 inflater.inflate(R.menu.newconversation_context, menu);
74 SparseBooleanArray checkedItems = contactsView
75 .getCheckedItemPositions();
76 selectedContacts.clear();
77 for (int i = 0; i < aggregatedContacts.size(); ++i) {
78 if (checkedItems.get(i, false)) {
79 selectedContacts.add(aggregatedContacts.get(i));
80 }
81 }
82 if (selectedContacts.size() == 0) {
83 menu.findItem(R.id.action_start_conversation).setVisible(false);
84 menu.findItem(R.id.action_contact_details).setVisible(false);
85 menu.findItem(R.id.action_invite).setVisible(false);
86 menu.findItem(R.id.action_invite_to_existing).setVisible(false);
87 } else if ((selectedContacts.size() == 1) && (!inviteIntent)) {
88 menu.findItem(R.id.action_start_conversation).setVisible(true);
89 menu.findItem(R.id.action_contact_details).setVisible(true);
90 menu.findItem(R.id.action_invite).setVisible(false);
91 menu.findItem(R.id.action_invite_to_existing).setVisible(true);
92 } else if (!inviteIntent) {
93 menu.findItem(R.id.action_start_conversation).setVisible(true);
94 menu.findItem(R.id.action_contact_details).setVisible(false);
95 menu.findItem(R.id.action_invite).setVisible(false);
96 menu.findItem(R.id.action_invite_to_existing).setVisible(true);
97 } else {
98 menu.findItem(R.id.action_invite).setVisible(true);
99 menu.findItem(R.id.action_start_conversation).setVisible(false);
100 menu.findItem(R.id.action_contact_details).setVisible(false);
101 menu.findItem(R.id.action_invite_to_existing).setVisible(false);
102 }
103 return true;
104 }
105
106 @Override
107 public void onDestroyActionMode(ActionMode mode) {
108 // TODO Auto-generated method stub
109
110 }
111
112 @Override
113 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
114 return true;
115 }
116
117 @Override
118 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
119 switch (item.getItemId()) {
120 case R.id.action_start_conversation:
121 if (selectedContacts.size() == 1) {
122 startConversation(selectedContacts.get(0));
123 } else {
124 startConference();
125 }
126 break;
127 case R.id.action_contact_details:
128 Intent intent = new Intent(getApplicationContext(),
129 ContactDetailsActivity.class);
130 intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
131 intent.putExtra("account", selectedContacts.get(0).getAccount().getJid());
132 intent.putExtra("contact",selectedContacts.get(0).getJid());
133 startActivity(intent);
134 finish();
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(getString(R.string.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,false);
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)&&(contact.showInRoster()))
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 Contact newContact = new Contact(searchString);
291 newContact.resetOption(Contact.Options.IN_ROSTER);
292 aggregatedContacts.add(newContact);
293 contactsHeader.setText(getString(R.string.new_contact));
294 } else {
295 contactsHeader.setText(getString(R.string.contacts));
296 }
297 } else {
298 contactsHeader.setText(getString(R.string.contacts));
299 }
300
301 contactsAdapter.notifyDataSetChanged();
302 contactsView.setScrollX(0);
303 }
304
305 private OnItemLongClickListener onLongClickListener = new OnItemLongClickListener() {
306
307 @Override
308 public boolean onItemLongClick(AdapterView<?> arg0, View view,
309 int position, long arg3) {
310 if (!isActionMode) {
311 contactsView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
312 contactsView.setItemChecked(position, true);
313 actionMode = contactsView.startActionMode(actionModeCallback);
314 }
315 return true;
316 }
317 };
318
319 @Override
320 protected void onStart() {
321 super.onStart();
322 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
323 this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
324 inviteIntent = "invite".equals(getIntent().getAction());
325 if (inviteIntent) {
326 contactsHeader.setVisibility(View.GONE);
327 actionMode = contactsView.startActionMode(actionModeCallback);
328 search.setVisibility(View.GONE);
329 }
330 }
331
332 @Override
333 protected void onCreate(Bundle savedInstanceState) {
334
335 super.onCreate(savedInstanceState);
336
337 setContentView(R.layout.activity_new_conversation);
338
339 contactsHeader = (TextView) findViewById(R.id.contacts_header);
340
341 search = (EditText) findViewById(R.id.new_conversation_search);
342 search.addTextChangedListener(new TextWatcher() {
343
344 @Override
345 public void onTextChanged(CharSequence s, int start, int before,
346 int count) {
347 searchString = search.getText().toString();
348 updateAggregatedContacts();
349 }
350
351 @Override
352 public void afterTextChanged(Editable s) {
353 // TODO Auto-generated method stub
354
355 }
356
357 @Override
358 public void beforeTextChanged(CharSequence s, int start, int count,
359 int after) {
360 // TODO Auto-generated method stub
361
362 }
363 });
364
365 contactsView = (ListView) findViewById(R.id.contactList);
366 contactsAdapter = new ArrayAdapter<Contact>(getApplicationContext(),
367 R.layout.contact, aggregatedContacts) {
368 @Override
369 public View getView(int position, View view, ViewGroup parent) {
370 LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
371 Contact contact = getItem(position);
372 if (view == null) {
373 view = (View) inflater.inflate(R.layout.contact, null);
374 }
375
376 ((TextView) view.findViewById(R.id.contact_display_name))
377 .setText(getItem(position).getDisplayName());
378 TextView contactJid = (TextView) view
379 .findViewById(R.id.contact_jid);
380 contactJid.setText(contact.getJid());
381 ImageView imageView = (ImageView) view
382 .findViewById(R.id.contact_photo);
383 imageView.setImageBitmap(UIHelper.getContactPicture(contact, 48, this.getContext(), false));
384 return view;
385 }
386 };
387 contactsView.setAdapter(contactsAdapter);
388 contactsView.setMultiChoiceModeListener(actionModeCallback);
389 contactsView.setOnItemClickListener(new OnItemClickListener() {
390
391 @Override
392 public void onItemClick(AdapterView<?> arg0, final View view,
393 int pos, long arg3) {
394 if (!isActionMode) {
395 Contact clickedContact = aggregatedContacts.get(pos);
396 startConversation(clickedContact);
397
398 } else {
399 actionMode.invalidate();
400 }
401 }
402 });
403 contactsView.setOnItemLongClickListener(this.onLongClickListener);
404 }
405
406 public void startConversation(final Contact contact) {
407 if ((contact.getAccount() == null) && (accounts.size() > 1)) {
408 getAccountChooser(new OnClickListener() {
409
410 @Override
411 public void onClick(DialogInterface dialog, int which) {
412 contact.setAccount(accounts.get(which));
413 showIsMucDialogIfNeeded(contact);
414 }
415 }).show();
416 } else {
417 if (contact.getAccount() == null) {
418 contact.setAccount(accounts.get(0));
419 }
420 showIsMucDialogIfNeeded(contact);
421 }
422 }
423
424 protected AlertDialog getAccountChooser(OnClickListener listener) {
425 String[] accountList = new String[accounts.size()];
426 for (int i = 0; i < accounts.size(); ++i) {
427 accountList[i] = accounts.get(i).getJid();
428 }
429
430 AlertDialog.Builder accountChooser = new AlertDialog.Builder(this);
431 accountChooser.setTitle(getString(R.string.choose_account));
432 accountChooser.setItems(accountList, listener);
433 return accountChooser.create();
434 }
435
436 public void showIsMucDialogIfNeeded(final Contact clickedContact) {
437 if (isMuc(clickedContact)) {
438 startConversation(clickedContact,clickedContact.getAccount(), true);
439 } else if (clickedContact.couldBeMuc()) {
440 AlertDialog.Builder dialog = new AlertDialog.Builder(this);
441 dialog.setTitle(getString(R.string.multi_user_conference));
442 dialog.setMessage(getString(R.string.trying_join_conference));
443 dialog.setPositiveButton(getString(R.string.yes), new OnClickListener() {
444
445 @Override
446 public void onClick(DialogInterface dialog, int which) {
447 startConversation(clickedContact,
448 clickedContact.getAccount(), true);
449 }
450 });
451 dialog.setNegativeButton(getString(R.string.no), new OnClickListener() {
452
453 @Override
454 public void onClick(DialogInterface dialog, int which) {
455 startConversation(clickedContact,
456 clickedContact.getAccount(), false);
457 }
458 });
459 dialog.create().show();
460 } else {
461 startConversation(clickedContact, clickedContact.getAccount(),
462 false);
463 }
464 }
465
466 private boolean isMuc(Contact contact) {
467 ArrayList<String> mucServers = new ArrayList<String>();
468 for(Account account : accounts) {
469 if (account.getXmppConnection()!=null) {
470 mucServers.add(account.getXmppConnection().getMucServer());
471 }
472 }
473 String server = contact.getJid().split("@")[1];
474 return mucServers.contains(server);
475 }
476
477 public void startConversation(Contact contact, Account account, boolean muc) {
478 if (!contact.getOption(Contact.Options.IN_ROSTER)&&(!muc)) {
479 xmppConnectionService.createContact(contact);
480 }
481 Conversation conversation = xmppConnectionService
482 .findOrCreateConversation(account, contact.getJid(), muc);
483
484 switchToConversation(conversation, null,false);
485 }
486
487 @Override
488 void onBackendConnected() {
489 this.accounts = xmppConnectionService.getAccounts();
490 if (Intent.ACTION_SENDTO.equals(getIntent().getAction())) {
491 getActionBar().setDisplayHomeAsUpEnabled(false);
492 getActionBar().setHomeButtonEnabled(false);
493 String jid;
494 try {
495 jid = URLDecoder.decode(getIntent().getData().getEncodedPath(),
496 "UTF-8").split("/")[1];
497 } catch (UnsupportedEncodingException e) {
498 jid = null;
499 }
500 if (jid != null) {
501 final String finalJid = jid;
502 if (this.accounts.size() > 1) {
503 getAccountChooser(new OnClickListener() {
504
505 @Override
506 public void onClick(DialogInterface dialog, int which) {
507 Conversation conversation = xmppConnectionService
508 .findOrCreateConversation(
509 accounts.get(which), finalJid,
510 false);
511 switchToConversation(conversation, null,false);
512 finish();
513 }
514 }).show();
515 } else {
516 Conversation conversation = xmppConnectionService
517 .findOrCreateConversation(this.accounts.get(0),
518 jid, false);
519 switchToConversation(conversation, null,false);
520 finish();
521 }
522 }
523 }
524
525 if (xmppConnectionService.getConversationCount() == 0) {
526 getActionBar().setDisplayHomeAsUpEnabled(false);
527 getActionBar().setHomeButtonEnabled(false);
528 }
529 this.rosterContacts.clear();
530 for(Account account : accounts) {
531 if (account.getStatus() != Account.STATUS_DISABLED) {
532 rosterContacts.addAll(account.getRoster().getContacts());
533 }
534 }
535 updateAggregatedContacts();
536 }
537
538 @Override
539 public boolean onCreateOptionsMenu(Menu menu) {
540 // Inflate the menu; this adds items to the action bar if it is present.
541 getMenuInflater().inflate(R.menu.newconversation, menu);
542 return true;
543 }
544
545 @Override
546 public boolean onOptionsItemSelected(MenuItem item) {
547 switch (item.getItemId()) {
548 default:
549 break;
550 }
551 return super.onOptionsItemSelected(item);
552 }
553
554 @Override
555 public void onActionModeStarted(ActionMode mode) {
556 super.onActionModeStarted(mode);
557 this.isActionMode = true;
558 search.setEnabled(false);
559 }
560
561 @Override
562 public void onActionModeFinished(ActionMode mode) {
563 super.onActionModeFinished(mode);
564 if (inviteIntent) {
565 finish();
566 } else {
567 this.isActionMode = false;
568 contactsView.clearChoices();
569 contactsView.requestLayout();
570 contactsView.post(new Runnable() {
571 @Override
572 public void run() {
573 contactsView.setChoiceMode(ListView.CHOICE_MODE_NONE);
574 }
575 });
576 search.setEnabled(true);
577 }
578 }
579
580}