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