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