XmppConnectionService.java

  1package de.gultsch.chat.services;
  2
  3import java.text.ParseException;
  4import java.text.SimpleDateFormat;
  5import java.util.ArrayList;
  6import java.util.Date;
  7import java.util.Hashtable;
  8import java.util.List;
  9import java.util.Set;
 10
 11import net.java.otr4j.OtrException;
 12import net.java.otr4j.session.Session;
 13import net.java.otr4j.session.SessionImpl;
 14import net.java.otr4j.session.SessionStatus;
 15
 16import de.gultsch.chat.entities.Account;
 17import de.gultsch.chat.entities.Contact;
 18import de.gultsch.chat.entities.Conversation;
 19import de.gultsch.chat.entities.Message;
 20import de.gultsch.chat.entities.Presences;
 21import de.gultsch.chat.persistance.DatabaseBackend;
 22import de.gultsch.chat.persistance.OnPhoneContactsMerged;
 23import de.gultsch.chat.ui.OnAccountListChangedListener;
 24import de.gultsch.chat.ui.OnConversationListChangedListener;
 25import de.gultsch.chat.ui.OnRosterFetchedListener;
 26import de.gultsch.chat.utils.MessageParser;
 27import de.gultsch.chat.utils.OnPhoneContactsLoadedListener;
 28import de.gultsch.chat.utils.PhoneHelper;
 29import de.gultsch.chat.utils.UIHelper;
 30import de.gultsch.chat.xml.Element;
 31import de.gultsch.chat.xmpp.IqPacket;
 32import de.gultsch.chat.xmpp.MessagePacket;
 33import de.gultsch.chat.xmpp.OnIqPacketReceived;
 34import de.gultsch.chat.xmpp.OnMessagePacketReceived;
 35import de.gultsch.chat.xmpp.OnPresencePacketReceived;
 36import de.gultsch.chat.xmpp.OnStatusChanged;
 37import de.gultsch.chat.xmpp.PresencePacket;
 38import de.gultsch.chat.xmpp.XmppConnection;
 39import android.app.NotificationManager;
 40import android.app.Service;
 41import android.content.Context;
 42import android.content.Intent;
 43import android.database.ContentObserver;
 44import android.database.DatabaseUtils;
 45import android.os.Binder;
 46import android.os.Bundle;
 47import android.os.IBinder;
 48import android.os.PowerManager;
 49import android.provider.ContactsContract;
 50import android.util.Log;
 51
 52public class XmppConnectionService extends Service {
 53
 54	protected static final String LOGTAG = "xmppService";
 55	public DatabaseBackend databaseBackend;
 56
 57	public long startDate;
 58
 59	private List<Account> accounts;
 60	private List<Conversation> conversations = null;
 61
 62	public OnConversationListChangedListener convChangedListener = null;
 63	private OnAccountListChangedListener accountChangedListener = null;
 64
 65	private ContentObserver contactObserver = new ContentObserver(null) {
 66		@Override
 67		public void onChange(boolean selfChange) {
 68			super.onChange(selfChange);
 69			Log.d(LOGTAG, "contact list has changed");
 70			mergePhoneContactsWithRoster(null);
 71		}
 72	};
 73
 74	private XmppConnectionService service = this;
 75
 76	private final IBinder mBinder = new XmppConnectionBinder();
 77	private OnMessagePacketReceived messageListener = new OnMessagePacketReceived() {
 78
 79		@Override
 80		public void onMessagePacketReceived(Account account,
 81				MessagePacket packet) {
 82			Message message = null;
 83			boolean notify = false;
 84			if ((packet.getType() == MessagePacket.TYPE_CHAT)) {
 85				if (packet.hasChild("body")
 86						&& (packet.getBody().startsWith("?OTR"))) {
 87					message = MessageParser.parseOtrChat(packet, account,
 88							service);
 89					notify = true;
 90				} else if (packet.hasChild("body")) {
 91					message = MessageParser.parsePlainTextChat(packet, account,
 92							service);
 93					notify = true;
 94				} else if (packet.hasChild("received")
 95						|| (packet.hasChild("sent"))) {
 96					message = MessageParser.parseCarbonMessage(packet, account,
 97							service);
 98				}
 99
100			} else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
101				message = MessageParser
102						.parseGroupchat(packet, account, service);
103				if (message != null) {
104					notify = (message.getStatus() == Message.STATUS_RECIEVED);
105				}
106			} else if (packet.getType() == MessagePacket.TYPE_ERROR) {
107				message = MessageParser.parseError(packet,account,service);
108			} else {
109				Log.d(LOGTAG, "unparsed message " + packet.toString());
110			}
111			if (message == null) {
112				return;
113			}
114			if (packet.hasChild("delay")) {
115				try {
116					String stamp = packet.findChild("delay").getAttribute(
117							"stamp");
118					stamp = stamp.replace("Z", "+0000");
119					Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
120							.parse(stamp);
121					message.setTime(date.getTime());
122				} catch (ParseException e) {
123					Log.d(LOGTAG, "error trying to parse date" + e.getMessage());
124				}
125			}
126			if (notify) {
127				message.markUnread();
128			}
129			Conversation conversation = message.getConversation();
130			conversation.getMessages().add(message);
131			if (packet.getType() != MessagePacket.TYPE_ERROR) {
132				databaseBackend.createMessage(message);
133			}
134			if (convChangedListener != null) {
135				convChangedListener.onConversationListChanged();
136			} else {
137				if (notify) {
138					NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
139					mNotificationManager.notify(2342, UIHelper
140							.getUnreadMessageNotification(
141									getApplicationContext(), conversation));
142				}
143			}
144		}
145	};
146	private OnStatusChanged statusListener = new OnStatusChanged() {
147
148		@Override
149		public void onStatusChanged(Account account) {
150			if (accountChangedListener != null) {
151				accountChangedListener.onAccountListChangedListener();
152			}
153			if (account.getStatus() == Account.STATUS_ONLINE) {
154				databaseBackend.clearPresences(account);
155				connectMultiModeConversations(account);
156				List<Conversation> conversations = getConversations();
157				for (int i = 0; i < conversations.size(); ++i) {
158					if (conversations.get(i).getAccount() == account) {
159						sendUnsendMessages(conversations.get(i));
160					}
161				}
162				if (convChangedListener != null) {
163					convChangedListener.onConversationListChanged();
164				}
165			}
166		}
167	};
168
169	private OnPresencePacketReceived presenceListener = new OnPresencePacketReceived() {
170
171		@Override
172		public void onPresencePacketReceived(Account account,
173				PresencePacket packet) {
174			String[] fromParts = packet.getAttribute("from").split("/");
175			Contact contact = findContact(account, fromParts[0]);
176			if (contact == null) {
177				// most likely muc, self or roster not synced
178				Log.d(LOGTAG,"got presence for non contact "+packet.toString());
179				return;
180			}
181			String type = packet.getAttribute("type");
182			if (type == null) {
183				Element show = packet.findChild("show");
184				if (show == null) {
185					contact.updatePresence(fromParts[1], Presences.ONLINE);
186				} else if (show.getContent().equals("away")) {
187					contact.updatePresence(fromParts[1], Presences.AWAY);
188				} else if (show.getContent().equals("xa")) {
189					contact.updatePresence(fromParts[1], Presences.XA);
190				} else if (show.getContent().equals("chat")) {
191					contact.updatePresence(fromParts[1], Presences.CHAT);
192				} else if (show.getContent().equals("dnd")) {
193					contact.updatePresence(fromParts[1], Presences.DND);
194				}
195				databaseBackend.updateContact(contact);
196			} else if (type.equals("unavailable")) {
197				if (fromParts.length != 2) {
198					// Log.d(LOGTAG,"received presence with no resource "+packet.toString());
199				} else {
200					contact.removePresence(fromParts[1]);
201					databaseBackend.updateContact(contact);
202				}
203			} else if (type.equals("subscribe")) {
204				Log.d(LOGTAG,account.getJid()+": "+contact.getJid()+" asked to subscribe");
205				if (contact.getSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT)) {
206					Log.d(LOGTAG,"preemptive grant existed. granting");
207					sendPresenceUpdatesTo(contact);
208					contact.setSubscriptionOption(Contact.Subscription.FROM);
209					contact.resetSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
210					replaceContactInConversation(contact.getJid(), contact);
211					databaseBackend.updateContact(contact);
212				} else {
213					//TODO: ask user to handle it maybe
214				}
215			} else {
216				Log.d(LOGTAG,packet.toString());
217			}
218			replaceContactInConversation(contact.getJid(),contact);
219		}
220	};
221
222	private OnIqPacketReceived unknownIqListener = new OnIqPacketReceived() {
223
224		@Override
225		public void onIqPacketReceived(Account account, IqPacket packet) {
226			if (packet.hasChild("query")) {
227				Element query = packet.findChild("query");
228				String xmlns = query.getAttribute("xmlns");
229				if ((xmlns != null) && (xmlns.equals("jabber:iq:roster"))) {
230					processRosterItems(account, query);
231					mergePhoneContactsWithRoster(null);
232				}
233			}
234		}
235	};
236
237	private void processRosterItems(Account account, Element elements) {
238		for (Element item : elements.getChildren()) {
239			if (item.getName().equals("item")) {
240				String jid = item.getAttribute("jid");
241				String subscription = item.getAttribute("subscription");
242				Contact contact = databaseBackend.findContact(account, jid);
243				if (contact == null) {
244					if (!subscription.equals("remove")) {
245						String name = item.getAttribute("name");
246						if (name == null) {
247							name = jid.split("@")[0];
248						}
249						contact = new Contact(account, name, jid, null);
250						contact.parseSubscriptionFromElement(item);
251						databaseBackend.createContact(contact);
252					}
253				} else {
254					if (subscription.equals("remove")) {
255						databaseBackend.deleteContact(contact);
256						replaceContactInConversation(contact.getJid(), null);
257					} else {
258						contact.parseSubscriptionFromElement(item);
259						databaseBackend.updateContact(contact);
260						replaceContactInConversation(contact.getJid(),contact);
261					}
262				}
263			}
264		}
265	}
266
267	private void replaceContactInConversation(String jid, Contact contact) {
268		List<Conversation> conversations = getConversations();
269		for (int i = 0; i < conversations.size(); ++i) {
270			if ((conversations.get(i).getContactJid().equals(jid))) {
271				conversations.get(i).setContact(contact);
272				break;
273			}
274		}
275	}
276
277	public class XmppConnectionBinder extends Binder {
278		public XmppConnectionService getService() {
279			return XmppConnectionService.this;
280		}
281	}
282
283	@Override
284	public int onStartCommand(Intent intent, int flags, int startId) {
285		for (Account account : accounts) {
286			if (account.getXmppConnection() == null) {
287				if (!account.isOptionSet(Account.OPTION_DISABLED)) {
288					account.setXmppConnection(this.createConnection(account));
289				}
290			}
291		}
292		return START_STICKY;
293	}
294
295	@Override
296	public void onCreate() {
297		databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
298		this.accounts = databaseBackend.getAccounts();
299
300		getContentResolver().registerContentObserver(
301				ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
302	}
303
304	@Override
305	public void onDestroy() {
306		super.onDestroy();
307		for (Account account : accounts) {
308			if (account.getXmppConnection() != null) {
309				disconnect(account);
310			}
311		}
312	}
313
314	public XmppConnection createConnection(Account account) {
315		PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
316		XmppConnection connection = new XmppConnection(account, pm);
317		connection.setOnMessagePacketReceivedListener(this.messageListener);
318		connection.setOnStatusChangedListener(this.statusListener);
319		connection.setOnPresencePacketReceivedListener(this.presenceListener);
320		connection
321				.setOnUnregisteredIqPacketReceivedListener(this.unknownIqListener);
322		Thread thread = new Thread(connection);
323		thread.start();
324		return connection;
325	}
326
327	public void sendMessage(Account account, Message message, String presence) {
328		Conversation conv = message.getConversation();
329		boolean saveInDb = false;
330		boolean addToConversation = false;
331		if (account.getStatus() == Account.STATUS_ONLINE) {
332			MessagePacket packet;
333			if (message.getEncryption() == Message.ENCRYPTION_OTR) {
334				if (!conv.hasValidOtrSession()) {
335					// starting otr session. messages will be send later
336					conv.startOtrSession(getApplicationContext(), presence);
337				} else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
338					// otr session aleary exists, creating message packet
339					// accordingly
340					packet = prepareMessagePacket(account, message,
341							conv.getOtrSession());
342					account.getXmppConnection().sendMessagePacket(packet);
343					message.setStatus(Message.STATUS_SEND);
344				}
345				saveInDb = true;
346				addToConversation = true;
347			} else {
348				// don't encrypt
349				if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
350					message.setStatus(Message.STATUS_SEND);
351					saveInDb = true;
352					addToConversation = true;
353				}
354
355				packet = prepareMessagePacket(account, message, null);
356				account.getXmppConnection().sendMessagePacket(packet);
357			}
358		} else {
359			// account is offline
360			saveInDb = true;
361			addToConversation = true;
362
363		}
364		if (saveInDb) {
365			databaseBackend.createMessage(message);
366		}
367		if (addToConversation) {
368			conv.getMessages().add(message);
369			if (convChangedListener != null) {
370				convChangedListener.onConversationListChanged();
371			}
372		}
373
374	}
375
376	private void sendUnsendMessages(Conversation conversation) {
377		for (int i = 0; i < conversation.getMessages().size(); ++i) {
378			if (conversation.getMessages().get(i).getStatus() == Message.STATUS_UNSEND) {
379				Message message = conversation.getMessages().get(i);
380				MessagePacket packet = prepareMessagePacket(
381						conversation.getAccount(), message, null);
382				conversation.getAccount().getXmppConnection()
383						.sendMessagePacket(packet);
384				message.setStatus(Message.STATUS_SEND);
385				if (conversation.getMode() == Conversation.MODE_SINGLE) {
386					databaseBackend.updateMessage(message);
387				} else {
388					databaseBackend.deleteMessage(message);
389					conversation.getMessages().remove(i);
390					i--;
391				}
392			}
393		}
394	}
395
396	public MessagePacket prepareMessagePacket(Account account, Message message,
397			Session otrSession) {
398		MessagePacket packet = new MessagePacket();
399		if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
400			packet.setType(MessagePacket.TYPE_CHAT);
401			if (otrSession != null) {
402				try {
403					packet.setBody(otrSession.transformSending(message
404							.getBody()));
405				} catch (OtrException e) {
406					Log.d(LOGTAG,
407							account.getJid()
408									+ ": could not encrypt message to "
409									+ message.getCounterpart());
410				}
411				Element privateMarker = new Element("private");
412				privateMarker.setAttribute("xmlns", "urn:xmpp:carbons:2");
413				packet.addChild(privateMarker);
414				packet.setTo(otrSession.getSessionID().getAccountID() + "/"
415						+ otrSession.getSessionID().getUserID());
416				packet.setFrom(account.getFullJid());
417			} else {
418				packet.setBody(message.getBody());
419				packet.setTo(message.getCounterpart());
420				packet.setFrom(account.getJid());
421			}
422		} else if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
423			packet.setType(MessagePacket.TYPE_GROUPCHAT);
424			packet.setBody(message.getBody());
425			packet.setTo(message.getCounterpart());
426			packet.setFrom(account.getJid());
427		}
428		return packet;
429	}
430
431	public void getRoster(Account account,
432			final OnRosterFetchedListener listener) {
433		List<Contact> contacts = databaseBackend.getContacts(account);
434		for (int i = 0; i < contacts.size(); ++i) {
435			contacts.get(i).setAccount(account);
436		}
437		if (listener != null) {
438			listener.onRosterFetched(contacts);
439		}
440	}
441
442	public void updateRoster(final Account account,
443			final OnRosterFetchedListener listener) {
444		IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
445		Element query = new Element("query");
446		query.setAttribute("xmlns", "jabber:iq:roster");
447		query.setAttribute("ver", account.getRosterVersion());
448		iqPacket.addChild(query);
449		account.getXmppConnection().sendIqPacket(iqPacket,
450				new OnIqPacketReceived() {
451
452					@Override
453					public void onIqPacketReceived(final Account account,
454							IqPacket packet) {
455						Element roster = packet.findChild("query");
456						if (roster != null) {
457							String version = roster.getAttribute("ver");
458							processRosterItems(account, roster);
459							if (version!=null) {
460								account.setRosterVersion(version);
461								databaseBackend.updateAccount(account);
462							} else {
463								StringBuilder mWhere = new StringBuilder();
464								mWhere.append("jid NOT IN(");
465								List<Element> items = roster.getChildren();
466								for(int i = 0; i < items.size(); ++i) {
467									mWhere.append("\"");
468									mWhere.append(DatabaseUtils.sqlEscapeString(items.get(i).getAttribute("jid")));
469									if (i != items.size() - 1) {
470										mWhere.append("\",");
471									} else {
472										mWhere.append("\"");
473									}
474								}
475								mWhere.append(") and accountUuid = \"");
476								mWhere.append(account.getUuid());
477								mWhere.append("\"");
478								List<Contact> contactsToDelete = databaseBackend.getContats(mWhere.toString());
479								for(Contact contact : contactsToDelete) {
480									databaseBackend.deleteContact(contact);
481									replaceContactInConversation(contact.getJid(), null);
482								}
483							}
484							mergePhoneContactsWithRoster(new OnPhoneContactsMerged() {
485								
486								@Override
487								public void phoneContactsMerged() {
488									if (listener != null) {
489										getRoster(account, listener);
490									}
491								}
492							});
493						} else {
494							if (listener != null) {
495								getRoster(account, listener);
496							}
497						}
498					}
499				});
500	}
501
502	public void mergePhoneContactsWithRoster(final OnPhoneContactsMerged listener) {
503		PhoneHelper.loadPhoneContacts(getApplicationContext(),
504				new OnPhoneContactsLoadedListener() {
505					@Override
506					public void onPhoneContactsLoaded(
507							Hashtable<String, Bundle> phoneContacts) {
508						List<Contact> contacts = databaseBackend
509								.getContacts(null);
510						for (int i = 0; i < contacts.size(); ++i) {
511							Contact contact = contacts.get(i);
512							if (phoneContacts.containsKey(contact.getJid())) {
513								Bundle phoneContact = phoneContacts.get(contact
514										.getJid());
515								String systemAccount = phoneContact
516										.getInt("phoneid")
517										+ "#"
518										+ phoneContact.getString("lookup");
519								contact.setSystemAccount(systemAccount);
520								contact.setPhotoUri(phoneContact
521										.getString("photouri"));
522								contact.setDisplayName(phoneContact
523										.getString("displayname"));
524								databaseBackend.updateContact(contact);
525								replaceContactInConversation(contact.getJid(), contact);
526							} else {
527								if ((contact.getSystemAccount() != null)
528										|| (contact.getProfilePhoto() != null)) {
529									contact.setSystemAccount(null);
530									contact.setPhotoUri(null);
531									databaseBackend.updateContact(contact);
532									replaceContactInConversation(contact.getJid(), contact);
533								}
534							}
535						}
536						if (listener!=null) {
537							listener.phoneContactsMerged();
538						}
539					}
540				});
541	}
542
543	public List<Conversation> getConversations() {
544		if (this.conversations == null) {
545			Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
546			for (Account account : this.accounts) {
547				accountLookupTable.put(account.getUuid(), account);
548			}
549			this.conversations = databaseBackend
550					.getConversations(Conversation.STATUS_AVAILABLE);
551			for (Conversation conv : this.conversations) {
552				Account account = accountLookupTable.get(conv.getAccountUuid());
553				conv.setAccount(account);
554				conv.setContact(findContact(account, conv.getContactJid()));
555				conv.setMessages(databaseBackend.getMessages(conv, 50));
556			}
557		}
558		return this.conversations;
559	}
560
561	public List<Account> getAccounts() {
562		return this.accounts;
563	}
564
565	public Contact findContact(Account account, String jid) {
566		Contact contact = databaseBackend.findContact(account, jid);
567		if (contact!=null) {
568			contact.setAccount(account);
569		}
570		return contact;
571	}
572
573	public Conversation findOrCreateConversation(Account account, String jid,
574			boolean muc) {
575		for (Conversation conv : this.getConversations()) {
576			if ((conv.getAccount().equals(account))
577					&& (conv.getContactJid().equals(jid))) {
578				return conv;
579			}
580		}
581		Conversation conversation = databaseBackend.findConversation(account,
582				jid);
583		if (conversation != null) {
584			conversation.setStatus(Conversation.STATUS_AVAILABLE);
585			conversation.setAccount(account);
586			if (muc) {
587				conversation.setMode(Conversation.MODE_MULTI);
588				if (account.getStatus() == Account.STATUS_ONLINE) {
589					joinMuc(conversation);
590				}
591			} else {
592				conversation.setMode(Conversation.MODE_SINGLE);
593			}
594			this.databaseBackend.updateConversation(conversation);
595			conversation.setContact(findContact(account,
596					conversation.getContactJid()));
597		} else {
598			String conversationName;
599			Contact contact = findContact(account, jid);
600			if (contact != null) {
601				conversationName = contact.getDisplayName();
602			} else {
603				conversationName = jid.split("@")[0];
604			}
605			if (muc) {
606				conversation = new Conversation(conversationName, account, jid,
607						Conversation.MODE_MULTI);
608				if (account.getStatus() == Account.STATUS_ONLINE) {
609					joinMuc(conversation);
610				}
611			} else {
612				conversation = new Conversation(conversationName, account, jid,
613						Conversation.MODE_SINGLE);
614			}
615			conversation.setContact(contact);
616			this.databaseBackend.createConversation(conversation);
617		}
618		this.conversations.add(conversation);
619		if (this.convChangedListener != null) {
620			this.convChangedListener.onConversationListChanged();
621		}
622		return conversation;
623	}
624
625	public void archiveConversation(Conversation conversation) {
626		if (conversation.getMode() == Conversation.MODE_MULTI) {
627			leaveMuc(conversation);
628		} else {
629			try {
630				conversation.endOtrIfNeeded();
631			} catch (OtrException e) {
632				Log.d(LOGTAG,
633						"error ending otr session for "
634								+ conversation.getName());
635			}
636		}
637		this.databaseBackend.updateConversation(conversation);
638		this.conversations.remove(conversation);
639		if (this.convChangedListener != null) {
640			this.convChangedListener.onConversationListChanged();
641		}
642	}
643
644	public int getConversationCount() {
645		return this.databaseBackend.getConversationCount();
646	}
647
648	public void createAccount(Account account) {
649		databaseBackend.createAccount(account);
650		this.accounts.add(account);
651		account.setXmppConnection(this.createConnection(account));
652		if (accountChangedListener != null)
653			accountChangedListener.onAccountListChangedListener();
654	}
655	
656	public void deleteContact(Contact contact) {
657		IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
658		Element query = new Element("query");
659		query.setAttribute("xmlns", "jabber:iq:roster");
660		Element item = new Element("item");
661		item.setAttribute("jid", contact.getJid());
662		item.setAttribute("subscription", "remove");
663		query.addChild(item);
664		iq.addChild(query);
665		contact.getAccount().getXmppConnection().sendIqPacket(iq, null);
666		replaceContactInConversation(contact.getJid(), null);
667		databaseBackend.deleteContact(contact);
668	}
669
670	public void updateAccount(Account account) {
671		databaseBackend.updateAccount(account);
672		if (account.getXmppConnection() != null) {
673			disconnect(account);
674		}
675		if (!account.isOptionSet(Account.OPTION_DISABLED)) {
676			account.setXmppConnection(this.createConnection(account));
677		}
678		if (accountChangedListener != null)
679			accountChangedListener.onAccountListChangedListener();
680	}
681
682	public void deleteAccount(Account account) {
683		Log.d(LOGTAG, "called delete account");
684		if (account.getXmppConnection() != null) {
685			this.disconnect(account);
686		}
687		databaseBackend.deleteAccount(account);
688		this.accounts.remove(account);
689		if (accountChangedListener != null)
690			accountChangedListener.onAccountListChangedListener();
691	}
692
693	public void setOnConversationListChangedListener(
694			OnConversationListChangedListener listener) {
695		this.convChangedListener = listener;
696	}
697
698	public void removeOnConversationListChangedListener() {
699		this.convChangedListener = null;
700	}
701
702	public void setOnAccountListChangedListener(
703			OnAccountListChangedListener listener) {
704		this.accountChangedListener = listener;
705	}
706
707	public void removeOnAccountListChangedListener() {
708		this.accountChangedListener = null;
709	}
710
711	public void connectMultiModeConversations(Account account) {
712		List<Conversation> conversations = getConversations();
713		for (int i = 0; i < conversations.size(); i++) {
714			Conversation conversation = conversations.get(i);
715			if ((conversation.getMode() == Conversation.MODE_MULTI)
716					&& (conversation.getAccount() == account)) {
717				joinMuc(conversation);
718			}
719		}
720	}
721
722	public void joinMuc(Conversation conversation) {
723		String muc = conversation.getContactJid();
724		PresencePacket packet = new PresencePacket();
725		packet.setAttribute("to", muc + "/"
726				+ conversation.getAccount().getUsername());
727		Element x = new Element("x");
728		x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
729		if (conversation.getMessages().size() != 0) {
730			Element history = new Element("history");
731			long lastMsgTime = conversation.getLatestMessage().getTimeSent();
732			long diff = (System.currentTimeMillis() - lastMsgTime) / 1000 - 1;
733			history.setAttribute("seconds", diff + "");
734			x.addChild(history);
735		}
736		packet.addChild(x);
737		conversation.getAccount().getXmppConnection()
738				.sendPresencePacket(packet);
739	}
740
741	public void leaveMuc(Conversation conversation) {
742
743	}
744
745	public void disconnect(Account account) {
746		List<Conversation> conversations = getConversations();
747		for (int i = 0; i < conversations.size(); i++) {
748			Conversation conversation = conversations.get(i);
749			if (conversation.getAccount() == account) {
750				if (conversation.getMode() == Conversation.MODE_MULTI) {
751					leaveMuc(conversation);
752				} else {
753					try {
754						conversation.endOtrIfNeeded();
755					} catch (OtrException e) {
756						Log.d(LOGTAG, "error ending otr session for "
757								+ conversation.getName());
758					}
759				}
760			}
761		}
762		account.getXmppConnection().disconnect();
763		Log.d(LOGTAG, "disconnected account: " + account.getJid());
764		account.setXmppConnection(null);
765	}
766
767	@Override
768	public IBinder onBind(Intent intent) {
769		return mBinder;
770	}
771
772	public void updateContact(Contact contact) {
773		databaseBackend.updateContact(contact);
774	}
775
776	public void createContact(Contact contact) {
777		IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
778		Element query = new Element("query");
779		query.setAttribute("xmlns", "jabber:iq:roster");
780		Element item = new Element("item");
781		item.setAttribute("jid", contact.getJid());
782		item.setAttribute("name", contact.getJid());
783		query.addChild(item);
784		iq.addChild(query);
785		Account account = contact.getAccount();
786		Log.d(LOGTAG,account.getJid()+": adding "+contact.getJid()+" to roster");
787		account.getXmppConnection().sendIqPacket(iq, null);
788		replaceContactInConversation(contact.getJid(), contact);
789		databaseBackend.createContact(contact);
790	}
791
792	public void requestPresenceUpdatesFrom(Contact contact) {
793		//Requesting a Subscription type=subscribe
794		PresencePacket packet = new PresencePacket();
795		packet.setAttribute("type", "subscribe");
796		packet.setAttribute("to", contact.getJid());
797		packet.setAttribute("from",contact.getAccount().getJid());
798		Log.d(LOGTAG,packet.toString());
799		contact.getAccount().getXmppConnection().sendPresencePacket(packet);
800	}
801	
802	public void stopPresenceUpdatesFrom(Contact contact) {
803		//Unsubscribing  type='unsubscribe'
804		PresencePacket packet = new PresencePacket();
805		packet.setAttribute("type", "unsubscribe");
806		packet.setAttribute("to", contact.getJid());
807		packet.setAttribute("from",contact.getAccount().getJid());
808		Log.d(LOGTAG,packet.toString());
809		contact.getAccount().getXmppConnection().sendPresencePacket(packet);
810	}
811	
812	public void stopPresenceUpdatesTo(Contact contact) {
813		//Canceling a Subscription type=unsubscribed
814		PresencePacket packet = new PresencePacket();
815		packet.setAttribute("type", "unsubscribed");
816		packet.setAttribute("to", contact.getJid());
817		packet.setAttribute("from",contact.getAccount().getJid());
818		Log.d(LOGTAG,packet.toString());
819		contact.getAccount().getXmppConnection().sendPresencePacket(packet);
820	}
821	
822	public void sendPresenceUpdatesTo(Contact contact) {
823		//type='subscribed'
824		PresencePacket packet = new PresencePacket();
825		packet.setAttribute("type", "subscribed");
826		packet.setAttribute("to", contact.getJid());
827		packet.setAttribute("from",contact.getAccount().getJid());
828		Log.d(LOGTAG,packet.toString());
829		contact.getAccount().getXmppConnection().sendPresencePacket(packet);
830	}
831}