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();
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 (isMuc(clickedContact)) {
437			startConversation(clickedContact,clickedContact.getAccount(), true);
438		} else if (clickedContact.couldBeMuc()) {
439			AlertDialog.Builder dialog = new AlertDialog.Builder(this);
440			dialog.setTitle(getString(R.string.multi_user_conference));
441			dialog.setMessage(getString(R.string.trying_join_conference));
442			dialog.setPositiveButton(getString(R.string.yes), new OnClickListener() {
443
444				@Override
445				public void onClick(DialogInterface dialog, int which) {
446					startConversation(clickedContact,
447							clickedContact.getAccount(), true);
448				}
449			});
450			dialog.setNegativeButton(getString(R.string.no), new OnClickListener() {
451
452				@Override
453				public void onClick(DialogInterface dialog, int which) {
454					startConversation(clickedContact,
455							clickedContact.getAccount(), false);
456				}
457			});
458			dialog.create().show();
459		} else {
460			startConversation(clickedContact, clickedContact.getAccount(),
461					false);
462		}
463	}
464	
465	private boolean isMuc(Contact contact) {
466		ArrayList<String> mucServers = new ArrayList<String>();
467		for(Account account : accounts) {
468			if (account.getXmppConnection()!=null) {
469				mucServers.add(account.getXmppConnection().getMucServer());
470			}
471		}
472		String server = contact.getJid().split("@")[1];
473		return mucServers.contains(server);
474	}
475
476	public void startConversation(Contact contact, Account account, boolean muc) {
477		if (!contact.getOption(Contact.Options.IN_ROSTER)&&(!muc)) {
478			xmppConnectionService.createContact(contact);
479		}
480		Conversation conversation = xmppConnectionService
481				.findOrCreateConversation(account, contact.getJid(), muc);
482
483		switchToConversation(conversation, null,false);
484	}
485
486	@Override
487	void onBackendConnected() {
488		this.accounts = xmppConnectionService.getAccounts();
489		if (Intent.ACTION_SENDTO.equals(getIntent().getAction())) {
490			getActionBar().setDisplayHomeAsUpEnabled(false);
491			getActionBar().setHomeButtonEnabled(false);
492			String jid;
493			try {
494				jid = URLDecoder.decode(getIntent().getData().getEncodedPath(),
495						"UTF-8").split("/")[1];
496			} catch (UnsupportedEncodingException e) {
497				jid = null;
498			}
499			if (jid != null) {
500				final String finalJid = jid;
501				if (this.accounts.size() > 1) {
502					getAccountChooser(new OnClickListener() {
503
504						@Override
505						public void onClick(DialogInterface dialog, int which) {
506							Conversation conversation = xmppConnectionService
507									.findOrCreateConversation(
508											accounts.get(which), finalJid,
509											false);
510							switchToConversation(conversation, null,false);
511							finish();
512						}
513					}).show();
514				} else {
515					Conversation conversation = xmppConnectionService
516							.findOrCreateConversation(this.accounts.get(0),
517									jid, false);
518					switchToConversation(conversation, null,false);
519					finish();
520				}
521			}
522		}
523
524		if (xmppConnectionService.getConversationCount() == 0) {
525			getActionBar().setDisplayHomeAsUpEnabled(false);
526			getActionBar().setHomeButtonEnabled(false);
527		}
528		this.rosterContacts.clear();
529		for(Account account : accounts) {
530			if (account.getStatus() != Account.STATUS_DISABLED) {
531				rosterContacts.addAll(account.getRoster().getContacts());
532			}
533		}
534		updateAggregatedContacts();
535	}
536
537	@Override
538	public boolean onCreateOptionsMenu(Menu menu) {
539		// Inflate the menu; this adds items to the action bar if it is present.
540		getMenuInflater().inflate(R.menu.newconversation, menu);
541		return true;
542	}
543
544	@Override
545	public boolean onOptionsItemSelected(MenuItem item) {
546		switch (item.getItemId()) {
547		default:
548			break;
549		}
550		return super.onOptionsItemSelected(item);
551	}
552
553	@Override
554	public void onActionModeStarted(ActionMode mode) {
555		super.onActionModeStarted(mode);
556		this.isActionMode = true;
557		search.setEnabled(false);
558	}
559
560	@Override
561	public void onActionModeFinished(ActionMode mode) {
562		super.onActionModeFinished(mode);
563		if (inviteIntent) {
564			finish();
565		} else {
566			this.isActionMode = false;
567			contactsView.clearChoices();
568			contactsView.requestLayout();
569			contactsView.post(new Runnable() {
570				@Override
571				public void run() {
572					contactsView.setChoiceMode(ListView.CHOICE_MODE_NONE);
573				}
574			});
575			search.setEnabled(true);
576		}
577	}
578
579}