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.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}