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(DatabaseUtils.sqlEscapeString(items.get(i).getAttribute("jid")));
468									if (i != items.size() - 1) {
469										mWhere.append(",");
470									}
471								}
472								mWhere.append(") and accountUuid = \"");
473								mWhere.append(account.getUuid());
474								mWhere.append("\"");
475								Log.d(LOGTAG,mWhere.toString());
476								List<Contact> contactsToDelete = databaseBackend.getContats(mWhere.toString());
477								for(Contact contact : contactsToDelete) {
478									databaseBackend.deleteContact(contact);
479									replaceContactInConversation(contact.getJid(), null);
480								}
481							}
482							mergePhoneContactsWithRoster(new OnPhoneContactsMerged() {
483								
484								@Override
485								public void phoneContactsMerged() {
486									if (listener != null) {
487										getRoster(account, listener);
488									}
489								}
490							});
491						} else {
492							if (listener != null) {
493								getRoster(account, listener);
494							}
495						}
496					}
497				});
498	}
499
500	public void mergePhoneContactsWithRoster(final OnPhoneContactsMerged listener) {
501		PhoneHelper.loadPhoneContacts(getApplicationContext(),
502				new OnPhoneContactsLoadedListener() {
503					@Override
504					public void onPhoneContactsLoaded(
505							Hashtable<String, Bundle> phoneContacts) {
506						List<Contact> contacts = databaseBackend
507								.getContacts(null);
508						for (int i = 0; i < contacts.size(); ++i) {
509							Contact contact = contacts.get(i);
510							if (phoneContacts.containsKey(contact.getJid())) {
511								Bundle phoneContact = phoneContacts.get(contact
512										.getJid());
513								String systemAccount = phoneContact
514										.getInt("phoneid")
515										+ "#"
516										+ phoneContact.getString("lookup");
517								contact.setSystemAccount(systemAccount);
518								contact.setPhotoUri(phoneContact
519										.getString("photouri"));
520								contact.setDisplayName(phoneContact
521										.getString("displayname"));
522								databaseBackend.updateContact(contact);
523								replaceContactInConversation(contact.getJid(), contact);
524							} else {
525								if ((contact.getSystemAccount() != null)
526										|| (contact.getProfilePhoto() != null)) {
527									contact.setSystemAccount(null);
528									contact.setPhotoUri(null);
529									databaseBackend.updateContact(contact);
530									replaceContactInConversation(contact.getJid(), contact);
531								}
532							}
533						}
534						if (listener!=null) {
535							listener.phoneContactsMerged();
536						}
537					}
538				});
539	}
540
541	public List<Conversation> getConversations() {
542		if (this.conversations == null) {
543			Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
544			for (Account account : this.accounts) {
545				accountLookupTable.put(account.getUuid(), account);
546			}
547			this.conversations = databaseBackend
548					.getConversations(Conversation.STATUS_AVAILABLE);
549			for (Conversation conv : this.conversations) {
550				Account account = accountLookupTable.get(conv.getAccountUuid());
551				conv.setAccount(account);
552				conv.setContact(findContact(account, conv.getContactJid()));
553				conv.setMessages(databaseBackend.getMessages(conv, 50));
554			}
555		}
556		return this.conversations;
557	}
558
559	public List<Account> getAccounts() {
560		return this.accounts;
561	}
562
563	public Contact findContact(Account account, String jid) {
564		Contact contact = databaseBackend.findContact(account, jid);
565		if (contact!=null) {
566			contact.setAccount(account);
567		}
568		return contact;
569	}
570
571	public Conversation findOrCreateConversation(Account account, String jid,
572			boolean muc) {
573		for (Conversation conv : this.getConversations()) {
574			if ((conv.getAccount().equals(account))
575					&& (conv.getContactJid().equals(jid))) {
576				return conv;
577			}
578		}
579		Conversation conversation = databaseBackend.findConversation(account,
580				jid);
581		if (conversation != null) {
582			conversation.setStatus(Conversation.STATUS_AVAILABLE);
583			conversation.setAccount(account);
584			if (muc) {
585				conversation.setMode(Conversation.MODE_MULTI);
586				if (account.getStatus() == Account.STATUS_ONLINE) {
587					joinMuc(conversation);
588				}
589			} else {
590				conversation.setMode(Conversation.MODE_SINGLE);
591			}
592			this.databaseBackend.updateConversation(conversation);
593			conversation.setContact(findContact(account,
594					conversation.getContactJid()));
595		} else {
596			String conversationName;
597			Contact contact = findContact(account, jid);
598			if (contact != null) {
599				conversationName = contact.getDisplayName();
600			} else {
601				conversationName = jid.split("@")[0];
602			}
603			if (muc) {
604				conversation = new Conversation(conversationName, account, jid,
605						Conversation.MODE_MULTI);
606				if (account.getStatus() == Account.STATUS_ONLINE) {
607					joinMuc(conversation);
608				}
609			} else {
610				conversation = new Conversation(conversationName, account, jid,
611						Conversation.MODE_SINGLE);
612			}
613			conversation.setContact(contact);
614			this.databaseBackend.createConversation(conversation);
615		}
616		this.conversations.add(conversation);
617		if (this.convChangedListener != null) {
618			this.convChangedListener.onConversationListChanged();
619		}
620		return conversation;
621	}
622
623	public void archiveConversation(Conversation conversation) {
624		if (conversation.getMode() == Conversation.MODE_MULTI) {
625			leaveMuc(conversation);
626		} else {
627			try {
628				conversation.endOtrIfNeeded();
629			} catch (OtrException e) {
630				Log.d(LOGTAG,
631						"error ending otr session for "
632								+ conversation.getName());
633			}
634		}
635		this.databaseBackend.updateConversation(conversation);
636		this.conversations.remove(conversation);
637		if (this.convChangedListener != null) {
638			this.convChangedListener.onConversationListChanged();
639		}
640	}
641
642	public int getConversationCount() {
643		return this.databaseBackend.getConversationCount();
644	}
645
646	public void createAccount(Account account) {
647		databaseBackend.createAccount(account);
648		this.accounts.add(account);
649		account.setXmppConnection(this.createConnection(account));
650		if (accountChangedListener != null)
651			accountChangedListener.onAccountListChangedListener();
652	}
653	
654	public void deleteContact(Contact contact) {
655		IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
656		Element query = new Element("query");
657		query.setAttribute("xmlns", "jabber:iq:roster");
658		Element item = new Element("item");
659		item.setAttribute("jid", contact.getJid());
660		item.setAttribute("subscription", "remove");
661		query.addChild(item);
662		iq.addChild(query);
663		contact.getAccount().getXmppConnection().sendIqPacket(iq, null);
664		replaceContactInConversation(contact.getJid(), null);
665		databaseBackend.deleteContact(contact);
666	}
667
668	public void updateAccount(Account account) {
669		databaseBackend.updateAccount(account);
670		if (account.getXmppConnection() != null) {
671			disconnect(account);
672		}
673		if (!account.isOptionSet(Account.OPTION_DISABLED)) {
674			account.setXmppConnection(this.createConnection(account));
675		}
676		if (accountChangedListener != null)
677			accountChangedListener.onAccountListChangedListener();
678	}
679
680	public void deleteAccount(Account account) {
681		Log.d(LOGTAG, "called delete account");
682		if (account.getXmppConnection() != null) {
683			this.disconnect(account);
684		}
685		databaseBackend.deleteAccount(account);
686		this.accounts.remove(account);
687		if (accountChangedListener != null)
688			accountChangedListener.onAccountListChangedListener();
689	}
690
691	public void setOnConversationListChangedListener(
692			OnConversationListChangedListener listener) {
693		this.convChangedListener = listener;
694	}
695
696	public void removeOnConversationListChangedListener() {
697		this.convChangedListener = null;
698	}
699
700	public void setOnAccountListChangedListener(
701			OnAccountListChangedListener listener) {
702		this.accountChangedListener = listener;
703	}
704
705	public void removeOnAccountListChangedListener() {
706		this.accountChangedListener = null;
707	}
708
709	public void connectMultiModeConversations(Account account) {
710		List<Conversation> conversations = getConversations();
711		for (int i = 0; i < conversations.size(); i++) {
712			Conversation conversation = conversations.get(i);
713			if ((conversation.getMode() == Conversation.MODE_MULTI)
714					&& (conversation.getAccount() == account)) {
715				joinMuc(conversation);
716			}
717		}
718	}
719
720	public void joinMuc(Conversation conversation) {
721		String muc = conversation.getContactJid();
722		PresencePacket packet = new PresencePacket();
723		packet.setAttribute("to", muc + "/"
724				+ conversation.getAccount().getUsername());
725		Element x = new Element("x");
726		x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
727		if (conversation.getMessages().size() != 0) {
728			Element history = new Element("history");
729			long lastMsgTime = conversation.getLatestMessage().getTimeSent();
730			long diff = (System.currentTimeMillis() - lastMsgTime) / 1000 - 1;
731			history.setAttribute("seconds", diff + "");
732			x.addChild(history);
733		}
734		packet.addChild(x);
735		conversation.getAccount().getXmppConnection()
736				.sendPresencePacket(packet);
737	}
738
739	public void leaveMuc(Conversation conversation) {
740
741	}
742
743	public void disconnect(Account account) {
744		List<Conversation> conversations = getConversations();
745		for (int i = 0; i < conversations.size(); i++) {
746			Conversation conversation = conversations.get(i);
747			if (conversation.getAccount() == account) {
748				if (conversation.getMode() == Conversation.MODE_MULTI) {
749					leaveMuc(conversation);
750				} else {
751					try {
752						conversation.endOtrIfNeeded();
753					} catch (OtrException e) {
754						Log.d(LOGTAG, "error ending otr session for "
755								+ conversation.getName());
756					}
757				}
758			}
759		}
760		account.getXmppConnection().disconnect();
761		Log.d(LOGTAG, "disconnected account: " + account.getJid());
762		account.setXmppConnection(null);
763	}
764
765	@Override
766	public IBinder onBind(Intent intent) {
767		return mBinder;
768	}
769
770	public void updateContact(Contact contact) {
771		databaseBackend.updateContact(contact);
772	}
773
774	public void createContact(Contact contact) {
775		IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
776		Element query = new Element("query");
777		query.setAttribute("xmlns", "jabber:iq:roster");
778		Element item = new Element("item");
779		item.setAttribute("jid", contact.getJid());
780		item.setAttribute("name", contact.getJid());
781		query.addChild(item);
782		iq.addChild(query);
783		Account account = contact.getAccount();
784		Log.d(LOGTAG,account.getJid()+": adding "+contact.getJid()+" to roster");
785		account.getXmppConnection().sendIqPacket(iq, null);
786		replaceContactInConversation(contact.getJid(), contact);
787		databaseBackend.createContact(contact);
788	}
789
790	public void requestPresenceUpdatesFrom(Contact contact) {
791		//Requesting a Subscription type=subscribe
792		PresencePacket packet = new PresencePacket();
793		packet.setAttribute("type", "subscribe");
794		packet.setAttribute("to", contact.getJid());
795		packet.setAttribute("from",contact.getAccount().getJid());
796		Log.d(LOGTAG,packet.toString());
797		contact.getAccount().getXmppConnection().sendPresencePacket(packet);
798	}
799	
800	public void stopPresenceUpdatesFrom(Contact contact) {
801		//Unsubscribing  type='unsubscribe'
802		PresencePacket packet = new PresencePacket();
803		packet.setAttribute("type", "unsubscribe");
804		packet.setAttribute("to", contact.getJid());
805		packet.setAttribute("from",contact.getAccount().getJid());
806		Log.d(LOGTAG,packet.toString());
807		contact.getAccount().getXmppConnection().sendPresencePacket(packet);
808	}
809	
810	public void stopPresenceUpdatesTo(Contact contact) {
811		//Canceling a Subscription type=unsubscribed
812		PresencePacket packet = new PresencePacket();
813		packet.setAttribute("type", "unsubscribed");
814		packet.setAttribute("to", contact.getJid());
815		packet.setAttribute("from",contact.getAccount().getJid());
816		Log.d(LOGTAG,packet.toString());
817		contact.getAccount().getXmppConnection().sendPresencePacket(packet);
818	}
819	
820	public void sendPresenceUpdatesTo(Contact contact) {
821		//type='subscribed'
822		PresencePacket packet = new PresencePacket();
823		packet.setAttribute("type", "subscribed");
824		packet.setAttribute("to", contact.getJid());
825		packet.setAttribute("from",contact.getAccount().getJid());
826		Log.d(LOGTAG,packet.toString());
827		contact.getAccount().getXmppConnection().sendPresencePacket(packet);
828	}
829}