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.hasValidOtrSession()) {
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							} else if ((before != after) && (after == SessionStatus.FINISHED)) {
166								conversation.resetOtrSession();
167								Log.d(LOGTAG,"otr session stoped");
168							}
169						} catch (Exception e) {
170							Log.d(LOGTAG, "error receiving otr. resetting");
171							conversation.resetOtrSession();
172							return;
173						}
174						if (body == null) {
175							return;
176						}
177						encryption = Message.ENCRYPTION_OTR;
178					}
179				}
180				Message message = new Message(conversation, counterPart, body,
181						encryption, status);
182				if (packet.hasChild("delay")) {
183					try {
184						String stamp = packet.findChild("delay").getAttribute(
185								"stamp");
186						stamp = stamp.replace("Z", "+0000");
187						Date date = new SimpleDateFormat(
188								"yyyy-MM-dd'T'HH:mm:ssZ").parse(stamp);
189						message.setTime(date.getTime());
190					} catch (ParseException e) {
191						Log.d(LOGTAG,
192								"error trying to parse date" + e.getMessage());
193					}
194				}
195				if (notify) {
196					message.markUnread();
197				}
198				conversation.getMessages().add(message);
199				databaseBackend.createMessage(message);
200				if (convChangedListener != null) {
201					convChangedListener.onConversationListChanged();
202				} else {
203					if (notify) {
204						NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
205						mNotificationManager.notify(2342, UIHelper
206								.getUnreadMessageNotification(
207										getApplicationContext(), conversation));
208					}
209				}
210			}
211		}
212	};
213	private OnStatusChanged statusListener = new OnStatusChanged() {
214
215		@Override
216		public void onStatusChanged(Account account) {
217			if (accountChangedListener != null) {
218				accountChangedListener.onAccountListChangedListener();
219			}
220			if (account.getStatus() == Account.STATUS_ONLINE) {
221				databaseBackend.clearPresences(account);
222				connectMultiModeConversations(account);
223				List<Conversation> conversations = getConversations();
224				for (int i = 0; i < conversations.size(); ++i) {
225					if (conversations.get(i).getAccount() == account) {
226						sendUnsendMessages(conversations.get(i));
227					}
228				}
229				if (convChangedListener != null) {
230					convChangedListener.onConversationListChanged();
231				}
232			}
233		}
234	};
235
236	private OnPresencePacketReceived presenceListener = new OnPresencePacketReceived() {
237
238		@Override
239		public void onPresencePacketReceived(Account account,
240				PresencePacket packet) {
241			String[] fromParts = packet.getAttribute("from").split("/");
242			Contact contact = findContact(account, fromParts[0]);
243			if (contact == null) {
244				// most likely muc, self or roster not synced
245				// Log.d(LOGTAG,"got presence for non contact "+packet.toString());
246				return;
247			}
248			String type = packet.getAttribute("type");
249			if (type == null) {
250				Element show = packet.findChild("show");
251				if (show == null) {
252					contact.updatePresence(fromParts[1], Presences.ONLINE);
253				} else if (show.getContent().equals("away")) {
254					contact.updatePresence(fromParts[1], Presences.AWAY);
255				} else if (show.getContent().equals("xa")) {
256					contact.updatePresence(fromParts[1], Presences.XA);
257				} else if (show.getContent().equals("chat")) {
258					contact.updatePresence(fromParts[1], Presences.CHAT);
259				} else if (show.getContent().equals("dnd")) {
260					contact.updatePresence(fromParts[1], Presences.DND);
261				}
262				databaseBackend.updateContact(contact);
263			} else if (type.equals("unavailable")) {
264				if (fromParts.length != 2) {
265					// Log.d(LOGTAG,"received presence with no resource "+packet.toString());
266				} else {
267					contact.removePresence(fromParts[1]);
268					databaseBackend.updateContact(contact);
269				}
270			}
271			replaceContactInConversation(contact);
272		}
273	};
274	
275	private void replaceContactInConversation(Contact contact) {
276		List<Conversation> conversations = getConversations();
277		for(int i = 0; i < conversations.size(); ++i) {
278			if (conversations.get(i).getContact().equals(contact)) {
279				conversations.get(i).setContact(contact);
280				break;
281			}
282		}
283	}
284
285	public class XmppConnectionBinder extends Binder {
286		public XmppConnectionService getService() {
287			return XmppConnectionService.this;
288		}
289	}
290
291	@Override
292	public int onStartCommand(Intent intent, int flags, int startId) {
293		for (Account account : accounts) {
294			if (account.getXmppConnection() == null) {
295				if (!account.isOptionSet(Account.OPTION_DISABLED)) {
296					account.setXmppConnection(this.createConnection(account));
297				}
298			}
299		}
300		return START_STICKY;
301	}
302
303	@Override
304	public void onCreate() {
305		databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
306		this.accounts = databaseBackend.getAccounts();
307
308		getContentResolver().registerContentObserver(
309				ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
310	}
311
312	@Override
313	public void onDestroy() {
314		super.onDestroy();
315		for (Account account : accounts) {
316			if (account.getXmppConnection() != null) {
317				disconnect(account);
318			}
319		}
320	}
321
322	public XmppConnection createConnection(Account account) {
323		PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
324		XmppConnection connection = new XmppConnection(account, pm);
325		connection.setOnMessagePacketReceivedListener(this.messageListener);
326		connection.setOnStatusChangedListener(this.statusListener);
327		connection.setOnPresencePacketReceivedListener(this.presenceListener);
328		Thread thread = new Thread(connection);
329		thread.start();
330		return connection;
331	}
332	
333	public void sendMessage(Account account, Message message, String presence) {
334		Conversation conv = message.getConversation();
335		boolean saveInDb = false;
336		boolean addToConversation = false;
337		if (account.getStatus() == Account.STATUS_ONLINE) {
338			MessagePacket packet;
339			if (message.getEncryption() == Message.ENCRYPTION_OTR) {
340				if (!conv.hasValidOtrSession()) {
341					//starting otr session. messages will be send later
342					conv.startOtrSession(getApplicationContext(), presence);
343				} else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED){
344					//otr session aleary exists, creating message packet accordingly
345					packet = prepareMessagePacket(account, message,
346							conv.getOtrSession());
347					account.getXmppConnection().sendMessagePacket(packet);
348					message.setStatus(Message.STATUS_SEND);
349				}
350				saveInDb = true;
351				addToConversation = true;
352			} else {
353				// don't encrypt
354				if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
355					message.setStatus(Message.STATUS_SEND);
356					saveInDb = true;
357					addToConversation = true;
358				}
359				
360				packet = prepareMessagePacket(account, message, null);
361				account.getXmppConnection().sendMessagePacket(packet);
362			}
363		} else {
364			// account is offline
365			saveInDb = true;
366			addToConversation = true;
367
368		}
369		if (saveInDb) {
370			databaseBackend.createMessage(message);
371		}
372		if (addToConversation) {
373			conv.getMessages().add(message);
374			if (convChangedListener != null) {
375				convChangedListener.onConversationListChanged();
376			}
377		}
378
379	}
380
381	private void sendUnsendMessages(Conversation conversation) {
382		for (int i = 0; i < conversation.getMessages().size(); ++i) {
383			if (conversation.getMessages().get(i).getStatus() == Message.STATUS_UNSEND) {
384				Message message = conversation.getMessages().get(i);
385				MessagePacket packet = prepareMessagePacket(
386						conversation.getAccount(), message, null);
387				conversation.getAccount().getXmppConnection()
388						.sendMessagePacket(packet);
389				message.setStatus(Message.STATUS_SEND);
390				if (conversation.getMode() == Conversation.MODE_SINGLE) {
391					databaseBackend.updateMessage(message);
392				} else {
393					databaseBackend.deleteMessage(message);
394					conversation.getMessages().remove(i);
395					i--;
396				}
397			}
398		}
399	}
400
401	private MessagePacket prepareMessagePacket(Account account,
402			Message message, Session otrSession) {
403		MessagePacket packet = new MessagePacket();
404		if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
405			packet.setType(MessagePacket.TYPE_CHAT);
406			if (otrSession != null) {
407				try {
408					packet.setBody(otrSession.transformSending(message
409							.getBody()));
410				} catch (OtrException e) {
411					Log.d(LOGTAG,
412							account.getJid()
413									+ ": could not encrypt message to "
414									+ message.getCounterpart());
415				}
416				Element privateMarker = new Element("private");
417				privateMarker.setAttribute("xmlns", "urn:xmpp:carbons:2");
418				packet.addChild(privateMarker);
419				packet.setTo(otrSession.getSessionID().getAccountID()+"/"+otrSession.getSessionID().getUserID());
420				packet.setFrom(account.getFullJid());
421			} else {
422				packet.setBody(message.getBody());
423				packet.setTo(message.getCounterpart());
424				packet.setFrom(account.getJid());
425			}
426		} else if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
427			packet.setType(MessagePacket.TYPE_GROUPCHAT);
428			packet.setBody(message.getBody());
429			packet.setTo(message.getCounterpart());
430			packet.setFrom(account.getJid());
431		}
432		return packet;
433	}
434
435	public void getRoster(Account account,
436			final OnRosterFetchedListener listener) {
437		List<Contact> contacts = databaseBackend.getContacts(account);
438		for (int i = 0; i < contacts.size(); ++i) {
439			contacts.get(i).setAccount(account);
440		}
441		if (listener != null) {
442			listener.onRosterFetched(contacts);
443		}
444	}
445
446	public void updateRoster(final Account account,
447			final OnRosterFetchedListener listener) {
448
449		PhoneHelper.loadPhoneContacts(this,
450				new OnPhoneContactsLoadedListener() {
451
452					@Override
453					public void onPhoneContactsLoaded(
454							final Hashtable<String, Bundle> phoneContacts) {
455						IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
456						Element query = new Element("query");
457						query.setAttribute("xmlns", "jabber:iq:roster");
458						query.setAttribute("ver", "");
459						iqPacket.addChild(query);
460						account.getXmppConnection().sendIqPacket(iqPacket,
461								new OnIqPacketReceived() {
462
463									@Override
464									public void onIqPacketReceived(
465											Account account, IqPacket packet) {
466										List<Contact> contacts = new ArrayList<Contact>();
467										Element roster = packet
468												.findChild("query");
469										if (roster != null) {
470											for (Element item : roster
471													.getChildren()) {
472												Contact contact;
473												String name = item
474														.getAttribute("name");
475												String jid = item
476														.getAttribute("jid");
477												if (phoneContacts
478														.containsKey(jid)) {
479													Bundle phoneContact = phoneContacts
480															.get(jid);
481													String systemAccount = phoneContact
482															.getInt("phoneid")
483															+ "#"
484															+ phoneContact
485																	.getString("lookup");
486													contact = new Contact(
487															account,
488															phoneContact
489																	.getString("displayname"),
490															jid,
491															phoneContact
492																	.getString("photouri"));
493													contact.setSystemAccount(systemAccount);
494												} else {
495													if (name == null) {
496														name = jid.split("@")[0];
497													}
498													contact = new Contact(
499															account, name, jid,
500															null);
501
502												}
503												contact.setAccount(account);
504												contact.setSubscription(item
505														.getAttribute("subscription"));
506												contacts.add(contact);
507											}
508											databaseBackend
509													.mergeContacts(contacts);
510											if (listener != null) {
511												listener.onRosterFetched(contacts);
512											}
513										}
514									}
515								});
516
517					}
518				});
519	}
520
521	public void mergePhoneContactsWithRoster() {
522		PhoneHelper.loadPhoneContacts(this,
523				new OnPhoneContactsLoadedListener() {
524					@Override
525					public void onPhoneContactsLoaded(
526							Hashtable<String, Bundle> phoneContacts) {
527						List<Contact> contacts = databaseBackend
528								.getContacts(null);
529						for (int i = 0; i < contacts.size(); ++i) {
530							Contact contact = contacts.get(i);
531							if (phoneContacts.containsKey(contact.getJid())) {
532								Bundle phoneContact = phoneContacts.get(contact
533										.getJid());
534								String systemAccount = phoneContact
535										.getInt("phoneid")
536										+ "#"
537										+ phoneContact.getString("lookup");
538								contact.setSystemAccount(systemAccount);
539								contact.setPhotoUri(phoneContact
540										.getString("photouri"));
541								contact.setDisplayName(phoneContact
542										.getString("displayname"));
543								databaseBackend.updateContact(contact);
544							} else {
545								if ((contact.getSystemAccount() != null)
546										|| (contact.getProfilePhoto() != null)) {
547									contact.setSystemAccount(null);
548									contact.setPhotoUri(null);
549									databaseBackend.updateContact(contact);
550								}
551							}
552						}
553					}
554				});
555	}
556
557	public void addConversation(Conversation conversation) {
558		databaseBackend.createConversation(conversation);
559	}
560
561	public List<Conversation> getConversations() {
562		if (this.conversations == null) {
563			Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
564			for (Account account : this.accounts) {
565				accountLookupTable.put(account.getUuid(), account);
566			}
567			this.conversations = databaseBackend
568					.getConversations(Conversation.STATUS_AVAILABLE);
569			for (Conversation conv : this.conversations) {
570				Account account = accountLookupTable.get(conv.getAccountUuid());
571				conv.setAccount(account);
572				conv.setContact(findContact(account, conv.getContactJid()));
573				conv.setMessages(databaseBackend.getMessages(conv, 50));
574			}
575		}
576		return this.conversations;
577	}
578
579	public List<Account> getAccounts() {
580		return this.accounts;
581	}
582
583	public Contact findContact(Account account, String jid) {
584		return databaseBackend.findContact(account, jid);
585	}
586
587	public Conversation findOrCreateConversation(Account account, String jid,
588			boolean muc) {
589		for (Conversation conv : this.getConversations()) {
590			if ((conv.getAccount().equals(account))
591					&& (conv.getContactJid().equals(jid))) {
592				return conv;
593			}
594		}
595		Conversation conversation = databaseBackend.findConversation(account,
596				jid);
597		if (conversation != null) {
598			conversation.setStatus(Conversation.STATUS_AVAILABLE);
599			conversation.setAccount(account);
600			if (muc) {
601				conversation.setMode(Conversation.MODE_MULTI);
602				if (account.getStatus() == Account.STATUS_ONLINE) {
603					joinMuc(conversation);
604				}
605			} else {
606				conversation.setMode(Conversation.MODE_SINGLE);
607			}
608			this.databaseBackend.updateConversation(conversation);
609			conversation.setContact(findContact(account, conversation.getContactJid()));
610		} else {
611			String conversationName;
612			Contact contact = findContact(account, jid);
613			if (contact != null) {
614				conversationName = contact.getDisplayName();
615			} else {
616				conversationName = jid.split("@")[0];
617			}
618			if (muc) {
619				conversation = new Conversation(conversationName, account, jid,
620						Conversation.MODE_MULTI);
621				if (account.getStatus() == Account.STATUS_ONLINE) {
622					joinMuc(conversation);
623				}
624			} else {
625				conversation = new Conversation(conversationName, account, jid,
626						Conversation.MODE_SINGLE);
627			}
628			conversation.setContact(contact);
629			this.databaseBackend.createConversation(conversation);
630		}
631		this.conversations.add(conversation);
632		if (this.convChangedListener != null) {
633			this.convChangedListener.onConversationListChanged();
634		}
635		return conversation;
636	}
637
638	public void archiveConversation(Conversation conversation) {
639		if (conversation.getMode() == Conversation.MODE_MULTI) {
640			leaveMuc(conversation);
641		} else {
642			try {
643				conversation.endOtrIfNeeded();
644			} catch (OtrException e) {
645				Log.d(LOGTAG,
646						"error ending otr session for "
647								+ conversation.getName());
648			}
649		}
650		this.databaseBackend.updateConversation(conversation);
651		this.conversations.remove(conversation);
652		if (this.convChangedListener != null) {
653			this.convChangedListener.onConversationListChanged();
654		}
655	}
656
657	public int getConversationCount() {
658		return this.databaseBackend.getConversationCount();
659	}
660
661	public void createAccount(Account account) {
662		databaseBackend.createAccount(account);
663		this.accounts.add(account);
664		account.setXmppConnection(this.createConnection(account));
665		if (accountChangedListener != null)
666			accountChangedListener.onAccountListChangedListener();
667	}
668
669	public void updateAccount(Account account) {
670		databaseBackend.updateAccount(account);
671		if (account.getXmppConnection() != null) {
672			disconnect(account);
673		}
674		if (!account.isOptionSet(Account.OPTION_DISABLED)) {
675			account.setXmppConnection(this.createConnection(account));
676		}
677		if (accountChangedListener != null)
678			accountChangedListener.onAccountListChangedListener();
679	}
680
681	public void deleteAccount(Account account) {
682		Log.d(LOGTAG, "called delete account");
683		if (account.getXmppConnection() != null) {
684			this.disconnect(account);
685		}
686		databaseBackend.deleteAccount(account);
687		this.accounts.remove(account);
688		if (accountChangedListener != null)
689			accountChangedListener.onAccountListChangedListener();
690	}
691
692	public void setOnConversationListChangedListener(
693			OnConversationListChangedListener listener) {
694		this.convChangedListener = listener;
695	}
696
697	public void removeOnConversationListChangedListener() {
698		this.convChangedListener = null;
699	}
700
701	public void setOnAccountListChangedListener(
702			OnAccountListChangedListener listener) {
703		this.accountChangedListener = listener;
704	}
705
706	public void removeOnAccountListChangedListener() {
707		this.accountChangedListener = null;
708	}
709
710	public void connectMultiModeConversations(Account account) {
711		List<Conversation> conversations = getConversations();
712		for (int i = 0; i < conversations.size(); i++) {
713			Conversation conversation = conversations.get(i);
714			if ((conversation.getMode() == Conversation.MODE_MULTI)
715					&& (conversation.getAccount() == account)) {
716				joinMuc(conversation);
717			}
718		}
719	}
720
721	public void joinMuc(Conversation conversation) {
722		String muc = conversation.getContactJid();
723		PresencePacket packet = new PresencePacket();
724		packet.setAttribute("to", muc + "/"
725				+ conversation.getAccount().getUsername());
726		Element x = new Element("x");
727		x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
728		if (conversation.getMessages().size() != 0) {
729			Element history = new Element("history");
730			long lastMsgTime = conversation.getLatestMessage().getTimeSent();
731			long diff = (System.currentTimeMillis() - lastMsgTime) / 1000;
732			history.setAttribute("seconds",diff+"");
733			x.addChild(history);
734		}
735		packet.addChild(x);
736		conversation.getAccount().getXmppConnection()
737				.sendPresencePacket(packet);
738	}
739
740	public void leaveMuc(Conversation conversation) {
741
742	}
743
744	public void disconnect(Account account) {
745		List<Conversation> conversations = getConversations();
746		for (int i = 0; i < conversations.size(); i++) {
747			Conversation conversation = conversations.get(i);
748			if (conversation.getAccount() == account) {
749				if (conversation.getMode() == Conversation.MODE_MULTI) {
750					leaveMuc(conversation);
751				} else {
752					try {
753						conversation.endOtrIfNeeded();
754					} catch (OtrException e) {
755						Log.d(LOGTAG, "error ending otr session for "
756								+ conversation.getName());
757					}
758				}
759			}
760		}
761		account.getXmppConnection().disconnect();
762		Log.d(LOGTAG, "disconnected account: " + account.getJid());
763		account.setXmppConnection(null);
764	}
765
766	@Override
767	public IBinder onBind(Intent intent) {
768		return mBinder;
769	}
770
771	public void updateContact(Contact contact) {
772		databaseBackend.updateContact(contact);
773	}
774}