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