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