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.ProgressBar;
 38import android.widget.TextView;
 39import android.widget.ImageView;
 40import android.widget.Toast;
 41import android.annotation.SuppressLint;
 42import android.app.AlertDialog;
 43import android.app.AlertDialog.Builder;
 44import android.content.Context;
 45import android.content.DialogInterface;
 46import android.content.SharedPreferences;
 47import android.content.DialogInterface.OnClickListener;
 48import android.content.Intent;
 49
 50public class ContactsActivity extends XmppActivity {
 51
 52	protected List<Contact> rosterContacts = new ArrayList<Contact>();
 53	protected List<Contact> aggregatedContacts = new ArrayList<Contact>();
 54	protected ListView contactsView;
 55	protected ArrayAdapter<Contact> contactsAdapter;
 56
 57	protected EditText search;
 58	protected String searchString = "";
 59	private TextView contactsHeader;
 60	private List<Account> accounts;
 61	private List<Contact> selectedContacts = new ArrayList<Contact>();
 62	
 63	private ContactsActivity activity = this;
 64
 65	private boolean useSubject = true;
 66	private boolean isActionMode = false;
 67	private boolean inviteIntent = false;
 68	private ActionMode actionMode = null;
 69	private AbsListView.MultiChoiceModeListener actionModeCallback = new AbsListView.MultiChoiceModeListener() {
 70
 71		@Override
 72		public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
 73			menu.clear();
 74			MenuInflater inflater = mode.getMenuInflater();
 75			inflater.inflate(R.menu.newconversation_context, menu);
 76			SparseBooleanArray checkedItems = contactsView
 77					.getCheckedItemPositions();
 78			selectedContacts.clear();
 79			for (int i = 0; i < aggregatedContacts.size(); ++i) {
 80				if (checkedItems.get(i, false)) {
 81					selectedContacts.add(aggregatedContacts.get(i));
 82				}
 83			}
 84			if (selectedContacts.size() == 0) {
 85				menu.findItem(R.id.action_start_conversation).setVisible(false);
 86				menu.findItem(R.id.action_contact_details).setVisible(false);
 87				menu.findItem(R.id.action_invite).setVisible(false);
 88				menu.findItem(R.id.action_invite_to_existing).setVisible(false);
 89			} else if ((selectedContacts.size() == 1) && (!inviteIntent)) {
 90				menu.findItem(R.id.action_start_conversation).setVisible(true);
 91				menu.findItem(R.id.action_contact_details).setVisible(true);
 92				menu.findItem(R.id.action_invite).setVisible(false);
 93				menu.findItem(R.id.action_invite_to_existing).setVisible(true);
 94			} else if (!inviteIntent) {
 95				menu.findItem(R.id.action_start_conversation).setVisible(true);
 96				menu.findItem(R.id.action_contact_details).setVisible(false);
 97				menu.findItem(R.id.action_invite).setVisible(false);
 98				menu.findItem(R.id.action_invite_to_existing).setVisible(true);
 99			} else {
100				menu.findItem(R.id.action_invite).setVisible(true);
101				menu.findItem(R.id.action_start_conversation).setVisible(false);
102				menu.findItem(R.id.action_contact_details).setVisible(false);
103				menu.findItem(R.id.action_invite_to_existing).setVisible(false);
104			}
105			return true;
106		}
107
108		@Override
109		public void onDestroyActionMode(ActionMode mode) {
110			// TODO Auto-generated method stub
111
112		}
113
114		@Override
115		public boolean onCreateActionMode(ActionMode mode, Menu menu) {
116			return true;
117		}
118
119		@Override
120		public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
121			switch (item.getItemId()) {
122			case R.id.action_start_conversation:
123				if (selectedContacts.size() == 1) {
124					startConversation(selectedContacts.get(0));
125				} else {
126					startConference();
127				}
128				break;
129			case R.id.action_contact_details:
130				Intent intent = new Intent(getApplicationContext(),
131						ContactDetailsActivity.class);
132				intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
133				intent.putExtra("uuid", selectedContacts.get(0).getUuid());
134				startActivity(intent);
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("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);
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))
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				String name = searchString.split("@")[0];
291				Contact newContact = new Contact(null, name, searchString, null);
292				newContact.flagAsNotInRoster();
293				aggregatedContacts.add(newContact);
294				contactsHeader.setText("Create new contact");
295			} else {
296				contactsHeader.setText("Contacts");
297			}
298		} else {
299			contactsHeader.setText("Contacts");
300		}
301
302		contactsAdapter.notifyDataSetChanged();
303		contactsView.setScrollX(0);
304	}
305
306	private OnItemLongClickListener onLongClickListener = new OnItemLongClickListener() {
307
308		@Override
309		public boolean onItemLongClick(AdapterView<?> arg0, View view,
310				int position, long arg3) {
311			if (!isActionMode) {
312				contactsView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
313				contactsView.setItemChecked(position, true);
314				actionMode = contactsView.startActionMode(actionModeCallback);
315			}
316			return true;
317		}
318	};
319
320	@Override
321	protected void onStart() {
322		super.onStart();
323		SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
324		this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
325		inviteIntent = "invite".equals(getIntent().getAction());
326		if (inviteIntent) {
327			contactsHeader.setVisibility(View.GONE);
328			actionMode = contactsView.startActionMode(actionModeCallback);
329			search.setVisibility(View.GONE);
330		}
331	}
332
333	@Override
334	protected void onCreate(Bundle savedInstanceState) {
335
336		super.onCreate(savedInstanceState);
337
338		setContentView(R.layout.activity_new_conversation);
339
340		contactsHeader = (TextView) findViewById(R.id.contacts_header);
341
342		search = (EditText) findViewById(R.id.new_conversation_search);
343		search.addTextChangedListener(new TextWatcher() {
344
345			@Override
346			public void onTextChanged(CharSequence s, int start, int before,
347					int count) {
348				searchString = search.getText().toString();
349				updateAggregatedContacts();
350			}
351
352			@Override
353			public void afterTextChanged(Editable s) {
354				// TODO Auto-generated method stub
355
356			}
357
358			@Override
359			public void beforeTextChanged(CharSequence s, int start, int count,
360					int after) {
361				// TODO Auto-generated method stub
362
363			}
364		});
365
366		contactsView = (ListView) findViewById(R.id.contactList);
367		contactsAdapter = new ArrayAdapter<Contact>(getApplicationContext(),
368				R.layout.contact, aggregatedContacts) {
369			@Override
370			public View getView(int position, View view, ViewGroup parent) {
371				LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
372				Contact contact = getItem(position);
373				if (view == null) {
374					view = (View) inflater.inflate(R.layout.contact, null);
375				}
376
377				((TextView) view.findViewById(R.id.contact_display_name))
378						.setText(getItem(position).getDisplayName());
379				TextView contactJid = (TextView) view
380						.findViewById(R.id.contact_jid);
381				contactJid.setText(contact.getJid());
382				ImageView imageView = (ImageView) view
383						.findViewById(R.id.contact_photo);
384				imageView.setImageBitmap(UIHelper.getContactPicture(contact, 48, this.getContext(), false));
385				return view;
386			}
387		};
388		contactsView.setAdapter(contactsAdapter);
389		contactsView.setMultiChoiceModeListener(actionModeCallback);
390		contactsView.setOnItemClickListener(new OnItemClickListener() {
391
392			@Override
393			public void onItemClick(AdapterView<?> arg0, final View view,
394					int pos, long arg3) {
395				if (!isActionMode) {
396					Contact clickedContact = aggregatedContacts.get(pos);
397					startConversation(clickedContact);
398
399				} else {
400					actionMode.invalidate();
401				}
402			}
403		});
404		contactsView.setOnItemLongClickListener(this.onLongClickListener);
405	}
406
407	public void startConversation(final Contact contact) {
408		if ((contact.getAccount() == null) && (accounts.size() > 1)) {
409			getAccountChooser(new OnClickListener() {
410
411				@Override
412				public void onClick(DialogInterface dialog, int which) {
413					contact.setAccount(accounts.get(which));
414					showIsMucDialogIfNeeded(contact);
415				}
416			}).show();
417		} else {
418			if (contact.getAccount() == null) {
419				contact.setAccount(accounts.get(0));
420			}
421			showIsMucDialogIfNeeded(contact);
422		}
423	}
424
425	protected AlertDialog getAccountChooser(OnClickListener listener) {
426		String[] accountList = new String[accounts.size()];
427		for (int i = 0; i < accounts.size(); ++i) {
428			accountList[i] = accounts.get(i).getJid();
429		}
430
431		AlertDialog.Builder accountChooser = new AlertDialog.Builder(this);
432		accountChooser.setTitle("Choose account");
433		accountChooser.setItems(accountList, listener);
434		return accountChooser.create();
435	}
436
437	public void showIsMucDialogIfNeeded(final Contact clickedContact) {
438		if (clickedContact.couldBeMuc()) {
439			AlertDialog.Builder dialog = new AlertDialog.Builder(this);
440			dialog.setTitle("Multi User Conference");
441			dialog.setMessage("Are you trying to join a conference?");
442			dialog.setPositiveButton("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("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	public void startConversation(Contact contact, Account account, boolean muc) {
466		if (!contact.isInRoster()&&(!muc)) {
467			xmppConnectionService.createContact(contact);
468		}
469		Conversation conversation = xmppConnectionService
470				.findOrCreateConversation(account, contact.getJid(), muc);
471
472		switchToConversation(conversation, null);
473	}
474
475	@Override
476	void onBackendConnected() {
477		this.accounts = xmppConnectionService.getAccounts();
478		if (Intent.ACTION_SENDTO.equals(getIntent().getAction())) {
479			getActionBar().setDisplayHomeAsUpEnabled(false);
480			getActionBar().setHomeButtonEnabled(false);
481			String jid;
482			try {
483				jid = URLDecoder.decode(getIntent().getData().getEncodedPath(),
484						"UTF-8").split("/")[1];
485			} catch (UnsupportedEncodingException e) {
486				jid = null;
487			}
488			if (jid != null) {
489				final String finalJid = jid;
490				if (this.accounts.size() > 1) {
491					getAccountChooser(new OnClickListener() {
492
493						@Override
494						public void onClick(DialogInterface dialog, int which) {
495							Conversation conversation = xmppConnectionService
496									.findOrCreateConversation(
497											accounts.get(which), finalJid,
498											false);
499							switchToConversation(conversation, null);
500							finish();
501						}
502					}).show();
503				} else {
504					Conversation conversation = xmppConnectionService
505							.findOrCreateConversation(this.accounts.get(0),
506									jid, false);
507					switchToConversation(conversation, null);
508					finish();
509				}
510			}
511		}
512
513		if (xmppConnectionService.getConversationCount() == 0) {
514			getActionBar().setDisplayHomeAsUpEnabled(false);
515			getActionBar().setHomeButtonEnabled(false);
516		}
517		this.rosterContacts.clear();
518		for(Account account : accounts) {
519			if (account.getStatus() != Account.STATUS_DISABLED) {
520				rosterContacts.addAll(xmppConnectionService.getRoster(account));
521			}
522		}
523		updateAggregatedContacts();
524	}
525
526	@Override
527	public boolean onCreateOptionsMenu(Menu menu) {
528		// Inflate the menu; this adds items to the action bar if it is present.
529		getMenuInflater().inflate(R.menu.newconversation, menu);
530		return true;
531	}
532
533	@Override
534	public boolean onOptionsItemSelected(MenuItem item) {
535		switch (item.getItemId()) {
536		case R.id.action_refresh_contacts:
537			refreshContacts();
538			break;
539		default:
540			break;
541		}
542		return super.onOptionsItemSelected(item);
543	}
544
545	private void refreshContacts() {
546		final ProgressBar progress = (ProgressBar) findViewById(R.id.progressBar1);
547		final EditText searchBar = (EditText) findViewById(R.id.new_conversation_search);
548		final TextView contactsHeader = (TextView) findViewById(R.id.contacts_header);
549		final ListView contactList = (ListView) findViewById(R.id.contactList);
550		searchBar.setVisibility(View.GONE);
551		contactsHeader.setVisibility(View.GONE);
552		contactList.setVisibility(View.GONE);
553		progress.setVisibility(View.VISIBLE);
554		this.accounts = xmppConnectionService.getAccounts();
555		this.rosterContacts.clear();
556		for (int i = 0; i < accounts.size(); ++i) {
557			if (accounts.get(i).getStatus() == Account.STATUS_ONLINE) {
558				xmppConnectionService.updateRoster(accounts.get(i),
559						new OnRosterFetchedListener() {
560
561							@Override
562							public void onRosterFetched(
563									final List<Contact> roster) {
564								runOnUiThread(new Runnable() {
565
566									@Override
567									public void run() {
568										rosterContacts.addAll(roster);
569										progress.setVisibility(View.GONE);
570										searchBar.setVisibility(View.VISIBLE);
571										contactList.setVisibility(View.VISIBLE);
572										contactList.setVisibility(View.VISIBLE);
573										updateAggregatedContacts();
574									}
575								});
576							}
577						});
578			}
579		}
580	}
581
582	@Override
583	public void onActionModeStarted(ActionMode mode) {
584		super.onActionModeStarted(mode);
585		this.isActionMode = true;
586		search.setEnabled(false);
587	}
588
589	@Override
590	public void onActionModeFinished(ActionMode mode) {
591		super.onActionModeFinished(mode);
592		if (inviteIntent) {
593			finish();
594		} else {
595			this.isActionMode = false;
596			contactsView.clearChoices();
597			contactsView.requestLayout();
598			contactsView.post(new Runnable() {
599				@Override
600				public void run() {
601					contactsView.setChoiceMode(ListView.CHOICE_MODE_NONE);
602				}
603			});
604			search.setEnabled(true);
605		}
606	}
607
608}