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();
241 String serverName = account.getXmppConnection()
242 .getMucServer();
243 String jid = mucName + "@" + serverName;
244 Conversation conversation = xmppConnectionService
245 .findOrCreateConversation(account, jid, true);
246 StringBuilder subject = new StringBuilder();
247 subject.append(account.getUsername() + ", ");
248 for (int i = 0; i < selectedContacts.size(); ++i) {
249 if (i + 1 != selectedContacts.size()) {
250 subject.append(selectedContacts.get(i)
251 .getDisplayName() + ", ");
252 } else {
253 subject.append(selectedContacts.get(i)
254 .getDisplayName());
255 }
256 }
257 xmppConnectionService.sendConversationSubject(
258 conversation, subject.toString());
259 xmppConnectionService.inviteToConference(conversation,
260 selectedContacts);
261 switchToConversation(conversation, null,false);
262 }
263 });
264 builder.create().show();
265 }
266 }
267
268 protected void updateAggregatedContacts() {
269
270 aggregatedContacts.clear();
271 for (Contact contact : rosterContacts) {
272 if (contact.match(searchString)&&(contact.showInRoster()))
273 aggregatedContacts.add(contact);
274 }
275
276 Collections.sort(aggregatedContacts, new Comparator<Contact>() {
277
278 @SuppressLint("DefaultLocale")
279 @Override
280 public int compare(Contact lhs, Contact rhs) {
281 return lhs.getDisplayName().toLowerCase()
282 .compareTo(rhs.getDisplayName().toLowerCase());
283 }
284 });
285
286 if (aggregatedContacts.size() == 0) {
287
288 if (Validator.isValidJid(searchString)) {
289 Contact newContact = new Contact(searchString);
290 newContact.resetOption(Contact.Options.IN_ROSTER);
291 aggregatedContacts.add(newContact);
292 contactsHeader.setText(getString(R.string.new_contact));
293 } else {
294 contactsHeader.setText(getString(R.string.contacts));
295 }
296 } else {
297 contactsHeader.setText(getString(R.string.contacts));
298 }
299
300 contactsAdapter.notifyDataSetChanged();
301 contactsView.setScrollX(0);
302 }
303
304 private OnItemLongClickListener onLongClickListener = new OnItemLongClickListener() {
305
306 @Override
307 public boolean onItemLongClick(AdapterView<?> arg0, View view,
308 int position, long arg3) {
309 if (!isActionMode) {
310 contactsView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
311 contactsView.setItemChecked(position, true);
312 actionMode = contactsView.startActionMode(actionModeCallback);
313 }
314 return true;
315 }
316 };
317
318 @Override
319 protected void onStart() {
320 super.onStart();
321 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
322 this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
323 inviteIntent = "invite".equals(getIntent().getAction());
324 if (inviteIntent) {
325 contactsHeader.setVisibility(View.GONE);
326 actionMode = contactsView.startActionMode(actionModeCallback);
327 search.setVisibility(View.GONE);
328 }
329 }
330
331 @Override
332 protected void onCreate(Bundle savedInstanceState) {
333
334 super.onCreate(savedInstanceState);
335
336 setContentView(R.layout.activity_new_conversation);
337
338 contactsHeader = (TextView) findViewById(R.id.contacts_header);
339
340 search = (EditText) findViewById(R.id.new_conversation_search);
341 search.addTextChangedListener(new TextWatcher() {
342
343 @Override
344 public void onTextChanged(CharSequence s, int start, int before,
345 int count) {
346 searchString = search.getText().toString();
347 updateAggregatedContacts();
348 }
349
350 @Override
351 public void afterTextChanged(Editable s) {
352 // TODO Auto-generated method stub
353
354 }
355
356 @Override
357 public void beforeTextChanged(CharSequence s, int start, int count,
358 int after) {
359 // TODO Auto-generated method stub
360
361 }
362 });
363
364 contactsView = (ListView) findViewById(R.id.contactList);
365 contactsAdapter = new ArrayAdapter<Contact>(getApplicationContext(),
366 R.layout.contact, aggregatedContacts) {
367 @Override
368 public View getView(int position, View view, ViewGroup parent) {
369 LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
370 Contact contact = getItem(position);
371 if (view == null) {
372 view = (View) inflater.inflate(R.layout.contact, null);
373 }
374
375 ((TextView) view.findViewById(R.id.contact_display_name))
376 .setText(getItem(position).getDisplayName());
377 TextView contactJid = (TextView) view
378 .findViewById(R.id.contact_jid);
379 contactJid.setText(contact.getJid());
380 ImageView imageView = (ImageView) view
381 .findViewById(R.id.contact_photo);
382 imageView.setImageBitmap(UIHelper.getContactPicture(contact, 48, this.getContext(), false));
383 return view;
384 }
385 };
386 contactsView.setAdapter(contactsAdapter);
387 contactsView.setMultiChoiceModeListener(actionModeCallback);
388 contactsView.setOnItemClickListener(new OnItemClickListener() {
389
390 @Override
391 public void onItemClick(AdapterView<?> arg0, final View view,
392 int pos, long arg3) {
393 if (!isActionMode) {
394 Contact clickedContact = aggregatedContacts.get(pos);
395 startConversation(clickedContact);
396
397 } else {
398 actionMode.invalidate();
399 }
400 }
401 });
402 contactsView.setOnItemLongClickListener(this.onLongClickListener);
403 }
404
405 public void startConversation(final Contact contact) {
406 if ((contact.getAccount() == null) && (accounts.size() > 1)) {
407 getAccountChooser(new OnClickListener() {
408
409 @Override
410 public void onClick(DialogInterface dialog, int which) {
411 contact.setAccount(accounts.get(which));
412 showIsMucDialogIfNeeded(contact);
413 }
414 }).show();
415 } else {
416 if (contact.getAccount() == null) {
417 contact.setAccount(accounts.get(0));
418 }
419 showIsMucDialogIfNeeded(contact);
420 }
421 }
422
423 protected AlertDialog getAccountChooser(OnClickListener listener) {
424 String[] accountList = new String[accounts.size()];
425 for (int i = 0; i < accounts.size(); ++i) {
426 accountList[i] = accounts.get(i).getJid();
427 }
428
429 AlertDialog.Builder accountChooser = new AlertDialog.Builder(this);
430 accountChooser.setTitle(getString(R.string.choose_account));
431 accountChooser.setItems(accountList, listener);
432 return accountChooser.create();
433 }
434
435 public void showIsMucDialogIfNeeded(final Contact clickedContact) {
436 if (clickedContact.couldBeMuc()) {
437 AlertDialog.Builder dialog = new AlertDialog.Builder(this);
438 dialog.setTitle(getString(R.string.multi_user_conference));
439 dialog.setMessage(getString(R.string.trying_join_conference));
440 dialog.setPositiveButton(getString(R.string.yes), new OnClickListener() {
441
442 @Override
443 public void onClick(DialogInterface dialog, int which) {
444 startConversation(clickedContact,
445 clickedContact.getAccount(), true);
446 }
447 });
448 dialog.setNegativeButton(getString(R.string.no), new OnClickListener() {
449
450 @Override
451 public void onClick(DialogInterface dialog, int which) {
452 startConversation(clickedContact,
453 clickedContact.getAccount(), false);
454 }
455 });
456 dialog.create().show();
457 } else {
458 startConversation(clickedContact, clickedContact.getAccount(),
459 false);
460 }
461 }
462
463 public void startConversation(Contact contact, Account account, boolean muc) {
464 if (!contact.getOption(Contact.Options.IN_ROSTER)&&(!muc)) {
465 xmppConnectionService.createContact(contact);
466 }
467 Conversation conversation = xmppConnectionService
468 .findOrCreateConversation(account, contact.getJid(), muc);
469
470 switchToConversation(conversation, null,false);
471 }
472
473 @Override
474 void onBackendConnected() {
475 this.accounts = xmppConnectionService.getAccounts();
476 if (Intent.ACTION_SENDTO.equals(getIntent().getAction())) {
477 getActionBar().setDisplayHomeAsUpEnabled(false);
478 getActionBar().setHomeButtonEnabled(false);
479 String jid;
480 try {
481 jid = URLDecoder.decode(getIntent().getData().getEncodedPath(),
482 "UTF-8").split("/")[1];
483 } catch (UnsupportedEncodingException e) {
484 jid = null;
485 }
486 if (jid != null) {
487 final String finalJid = jid;
488 if (this.accounts.size() > 1) {
489 getAccountChooser(new OnClickListener() {
490
491 @Override
492 public void onClick(DialogInterface dialog, int which) {
493 Conversation conversation = xmppConnectionService
494 .findOrCreateConversation(
495 accounts.get(which), finalJid,
496 false);
497 switchToConversation(conversation, null,false);
498 finish();
499 }
500 }).show();
501 } else {
502 Conversation conversation = xmppConnectionService
503 .findOrCreateConversation(this.accounts.get(0),
504 jid, false);
505 switchToConversation(conversation, null,false);
506 finish();
507 }
508 }
509 }
510
511 if (xmppConnectionService.getConversationCount() == 0) {
512 getActionBar().setDisplayHomeAsUpEnabled(false);
513 getActionBar().setHomeButtonEnabled(false);
514 }
515 this.rosterContacts.clear();
516 for(Account account : accounts) {
517 if (account.getStatus() != Account.STATUS_DISABLED) {
518 rosterContacts.addAll(account.getRoster().getContacts());
519 }
520 }
521 updateAggregatedContacts();
522 }
523
524 @Override
525 public boolean onCreateOptionsMenu(Menu menu) {
526 // Inflate the menu; this adds items to the action bar if it is present.
527 getMenuInflater().inflate(R.menu.newconversation, menu);
528 return true;
529 }
530
531 @Override
532 public boolean onOptionsItemSelected(MenuItem item) {
533 switch (item.getItemId()) {
534 default:
535 break;
536 }
537 return super.onOptionsItemSelected(item);
538 }
539
540 @Override
541 public void onActionModeStarted(ActionMode mode) {
542 super.onActionModeStarted(mode);
543 this.isActionMode = true;
544 search.setEnabled(false);
545 }
546
547 @Override
548 public void onActionModeFinished(ActionMode mode) {
549 super.onActionModeFinished(mode);
550 if (inviteIntent) {
551 finish();
552 } else {
553 this.isActionMode = false;
554 contactsView.clearChoices();
555 contactsView.requestLayout();
556 contactsView.post(new Runnable() {
557 @Override
558 public void run() {
559 contactsView.setChoiceMode(ListView.CHOICE_MODE_NONE);
560 }
561 });
562 search.setEnabled(true);
563 }
564 }
565
566}