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