ContactsActivity.java

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