XmppConnectionService.java

  1package de.gultsch.chat.services;
  2
  3import java.io.BufferedOutputStream;
  4import java.io.BufferedWriter;
  5import java.io.ByteArrayInputStream;
  6import java.io.ByteArrayOutputStream;
  7import java.io.FileInputStream;
  8import java.io.FileOutputStream;
  9import java.io.IOException;
 10import java.io.InputStream;
 11import java.io.OutputStream;
 12import java.io.UnsupportedEncodingException;
 13import java.io.Writer;
 14import java.text.ParseException;
 15import java.text.SimpleDateFormat;
 16import java.util.ArrayList;
 17import java.util.Date;
 18import java.util.Hashtable;
 19import java.util.List;
 20import java.util.Set;
 21
 22import org.json.JSONException;
 23import org.openintents.openpgp.IOpenPgpService;
 24import org.openintents.openpgp.OpenPgpSignatureResult;
 25import org.openintents.openpgp.util.OpenPgpApi;
 26import org.openintents.openpgp.util.OpenPgpServiceConnection;
 27import org.openintents.openpgp.OpenPgpError;
 28
 29import net.java.otr4j.OtrException;
 30import net.java.otr4j.session.Session;
 31import net.java.otr4j.session.SessionImpl;
 32import net.java.otr4j.session.SessionStatus;
 33
 34import de.gultsch.chat.crypto.PgpEngine;
 35import de.gultsch.chat.crypto.PgpEngine.OpenPgpException;
 36import de.gultsch.chat.crypto.PgpEngine.UserInputRequiredException;
 37import de.gultsch.chat.entities.Account;
 38import de.gultsch.chat.entities.Contact;
 39import de.gultsch.chat.entities.Conversation;
 40import de.gultsch.chat.entities.Message;
 41import de.gultsch.chat.entities.Presences;
 42import de.gultsch.chat.persistance.DatabaseBackend;
 43import de.gultsch.chat.persistance.OnPhoneContactsMerged;
 44import de.gultsch.chat.ui.OnAccountListChangedListener;
 45import de.gultsch.chat.ui.OnConversationListChangedListener;
 46import de.gultsch.chat.ui.OnRosterFetchedListener;
 47import de.gultsch.chat.utils.MessageParser;
 48import de.gultsch.chat.utils.OnPhoneContactsLoadedListener;
 49import de.gultsch.chat.utils.PhoneHelper;
 50import de.gultsch.chat.utils.UIHelper;
 51import de.gultsch.chat.xml.Element;
 52import de.gultsch.chat.xmpp.IqPacket;
 53import de.gultsch.chat.xmpp.MessagePacket;
 54import de.gultsch.chat.xmpp.OnIqPacketReceived;
 55import de.gultsch.chat.xmpp.OnMessagePacketReceived;
 56import de.gultsch.chat.xmpp.OnPresencePacketReceived;
 57import de.gultsch.chat.xmpp.OnStatusChanged;
 58import de.gultsch.chat.xmpp.PresencePacket;
 59import de.gultsch.chat.xmpp.XmppConnection;
 60import android.app.NotificationManager;
 61import android.app.Service;
 62import android.content.Context;
 63import android.content.Intent;
 64import android.content.SharedPreferences;
 65import android.database.ContentObserver;
 66import android.database.DatabaseUtils;
 67import android.os.Binder;
 68import android.os.Bundle;
 69import android.os.IBinder;
 70import android.os.ParcelFileDescriptor;
 71import android.os.PowerManager;
 72import android.os.RemoteException;
 73import android.preference.PreferenceManager;
 74import android.provider.ContactsContract;
 75import android.util.Log;
 76
 77public class XmppConnectionService extends Service {
 78
 79	protected static final String LOGTAG = "xmppService";
 80	public DatabaseBackend databaseBackend;
 81
 82	public long startDate;
 83
 84	private List<Account> accounts;
 85	private List<Conversation> conversations = null;
 86
 87	public OnConversationListChangedListener convChangedListener = null;
 88	private OnAccountListChangedListener accountChangedListener = null;
 89
 90	private ContentObserver contactObserver = new ContentObserver(null) {
 91		@Override
 92		public void onChange(boolean selfChange) {
 93			super.onChange(selfChange);
 94			Log.d(LOGTAG, "contact list has changed");
 95			mergePhoneContactsWithRoster(null);
 96		}
 97	};
 98
 99	private XmppConnectionService service = this;
100
101	private final IBinder mBinder = new XmppConnectionBinder();
102	private OnMessagePacketReceived messageListener = new OnMessagePacketReceived() {
103
104		@Override
105		public void onMessagePacketReceived(Account account,
106				MessagePacket packet) {
107			Message message = null;
108			boolean notify = false;
109			if ((packet.getType() == MessagePacket.TYPE_CHAT)) {
110				String pgpBody = MessageParser.getPgpBody(packet);
111				if (pgpBody != null) {
112					message = MessageParser.parsePgpChat(pgpBody, packet,
113							account, service);
114					notify = false;
115				} else if (packet.hasChild("body")
116						&& (packet.getBody().startsWith("?OTR"))) {
117					message = MessageParser.parseOtrChat(packet, account,
118							service);
119					notify = true;
120				} else if (packet.hasChild("body")) {
121					message = MessageParser.parsePlainTextChat(packet, account,
122							service);
123					notify = true;
124				} else if (packet.hasChild("received")
125						|| (packet.hasChild("sent"))) {
126					message = MessageParser.parseCarbonMessage(packet, account,
127							service);
128				}
129
130			} else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
131				message = MessageParser
132						.parseGroupchat(packet, account, service);
133				if (message != null) {
134					notify = (message.getStatus() == Message.STATUS_RECIEVED);
135				}
136			} else if (packet.getType() == MessagePacket.TYPE_ERROR) {
137				message = MessageParser.parseError(packet, account, service);
138			} else {
139				Log.d(LOGTAG, "unparsed message " + packet.toString());
140			}
141			if (message == null) {
142				return;
143			}
144			if (packet.hasChild("delay")) {
145				try {
146					String stamp = packet.findChild("delay").getAttribute(
147							"stamp");
148					stamp = stamp.replace("Z", "+0000");
149					Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
150							.parse(stamp);
151					message.setTime(date.getTime());
152				} catch (ParseException e) {
153					Log.d(LOGTAG, "error trying to parse date" + e.getMessage());
154				}
155			}
156			if (notify) {
157				message.markUnread();
158			}
159			Conversation conversation = message.getConversation();
160			conversation.getMessages().add(message);
161			if (packet.getType() != MessagePacket.TYPE_ERROR) {
162				databaseBackend.createMessage(message);
163			}
164			if (convChangedListener != null) {
165				convChangedListener.onConversationListChanged();
166			} else {
167				if (notify) {
168					NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
169					mNotificationManager.notify(2342, UIHelper
170							.getUnreadMessageNotification(
171									getApplicationContext(), conversation));
172				}
173			}
174		}
175	};
176	private OnStatusChanged statusListener = new OnStatusChanged() {
177
178		@Override
179		public void onStatusChanged(Account account) {
180			if (accountChangedListener != null) {
181				accountChangedListener.onAccountListChangedListener();
182			}
183			if (account.getStatus() == Account.STATUS_ONLINE) {
184				databaseBackend.clearPresences(account);
185				connectMultiModeConversations(account);
186				List<Conversation> conversations = getConversations();
187				for (int i = 0; i < conversations.size(); ++i) {
188					if (conversations.get(i).getAccount() == account) {
189						sendUnsendMessages(conversations.get(i));
190					}
191				}
192				if (convChangedListener != null) {
193					convChangedListener.onConversationListChanged();
194				}
195				if (account.getKeys().has("pgp_signature")) {
196					try {
197						sendPgpPresence(account, account.getKeys().getString("pgp_signature"));
198					} catch (JSONException e) {
199						//
200					}
201				}
202			}
203		}
204	};
205
206	private OnPresencePacketReceived presenceListener = new OnPresencePacketReceived() {
207
208		@Override
209		public void onPresencePacketReceived(Account account,
210				PresencePacket packet) {
211			String[] fromParts = packet.getAttribute("from").split("/");
212			Contact contact = findContact(account, fromParts[0]);
213			if (contact == null) {
214				// most likely muc, self or roster not synced
215				Log.d(LOGTAG,
216						"got presence for non contact " + packet.toString());
217				return;
218			}
219			String type = packet.getAttribute("type");
220			if (type == null) {
221				Element show = packet.findChild("show");
222				if (show == null) {
223					contact.updatePresence(fromParts[1], Presences.ONLINE);
224				} else if (show.getContent().equals("away")) {
225					contact.updatePresence(fromParts[1], Presences.AWAY);
226				} else if (show.getContent().equals("xa")) {
227					contact.updatePresence(fromParts[1], Presences.XA);
228				} else if (show.getContent().equals("chat")) {
229					contact.updatePresence(fromParts[1], Presences.CHAT);
230				} else if (show.getContent().equals("dnd")) {
231					contact.updatePresence(fromParts[1], Presences.DND);
232				}
233				Element x = packet.findChild("x");
234				if ((x != null)
235						&& (x.getAttribute("xmlns").equals("jabber:x:signed"))) {
236					try {
237						Log.d(LOGTAG,"pgp signature for contact" +packet.getAttribute("from"));
238						contact.setPgpKeyId(getPgpEngine().fetchKeyId(packet.findChild("status")
239								.getContent(), x.getContent()));
240						databaseBackend.updateContact(contact);
241					} catch (OpenPgpException e) {
242						Log.d(LOGTAG,"faulty pgp. just ignore");
243					}
244				}
245				databaseBackend.updateContact(contact);
246			} else if (type.equals("unavailable")) {
247				if (fromParts.length != 2) {
248					// Log.d(LOGTAG,"received presence with no resource "+packet.toString());
249				} else {
250					contact.removePresence(fromParts[1]);
251					databaseBackend.updateContact(contact);
252				}
253			} else if (type.equals("subscribe")) {
254				if (contact
255						.getSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT)) {
256					sendPresenceUpdatesTo(contact);
257					contact.setSubscriptionOption(Contact.Subscription.FROM);
258					contact.resetSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
259					replaceContactInConversation(contact.getJid(), contact);
260					databaseBackend.updateContact(contact);
261					if ((contact
262							.getSubscriptionOption(Contact.Subscription.ASKING))
263							&& (!contact
264									.getSubscriptionOption(Contact.Subscription.TO))) {
265						requestPresenceUpdatesFrom(contact);
266					}
267				} else {
268					// TODO: ask user to handle it maybe
269				}
270			} else {
271				Log.d(LOGTAG, packet.toString());
272			}
273			replaceContactInConversation(contact.getJid(), contact);
274		}
275	};
276
277	private OnIqPacketReceived unknownIqListener = new OnIqPacketReceived() {
278
279		@Override
280		public void onIqPacketReceived(Account account, IqPacket packet) {
281			if (packet.hasChild("query")) {
282				Element query = packet.findChild("query");
283				String xmlns = query.getAttribute("xmlns");
284				if ((xmlns != null) && (xmlns.equals("jabber:iq:roster"))) {
285					processRosterItems(account, query);
286					mergePhoneContactsWithRoster(null);
287				}
288			}
289		}
290	};
291
292	private OpenPgpServiceConnection pgpServiceConnection;
293	private PgpEngine mPgpEngine = null;
294
295	public PgpEngine getPgpEngine() {
296		if (pgpServiceConnection.isBound()) {
297			if (this.mPgpEngine == null) {
298				this.mPgpEngine = new PgpEngine(new OpenPgpApi(
299						getApplicationContext(),
300						pgpServiceConnection.getService()));
301			}
302			return mPgpEngine;
303		} else {
304			return null;
305		}
306
307	}
308
309	private void processRosterItems(Account account, Element elements) {
310		for (Element item : elements.getChildren()) {
311			if (item.getName().equals("item")) {
312				String jid = item.getAttribute("jid");
313				String subscription = item.getAttribute("subscription");
314				Contact contact = databaseBackend.findContact(account, jid);
315				if (contact == null) {
316					if (!subscription.equals("remove")) {
317						String name = item.getAttribute("name");
318						if (name == null) {
319							name = jid.split("@")[0];
320						}
321						contact = new Contact(account, name, jid, null);
322						contact.parseSubscriptionFromElement(item);
323						databaseBackend.createContact(contact);
324					}
325				} else {
326					if (subscription.equals("remove")) {
327						databaseBackend.deleteContact(contact);
328						replaceContactInConversation(contact.getJid(), null);
329					} else {
330						contact.parseSubscriptionFromElement(item);
331						databaseBackend.updateContact(contact);
332						replaceContactInConversation(contact.getJid(), contact);
333					}
334				}
335			}
336		}
337	}
338
339	private void replaceContactInConversation(String jid, Contact contact) {
340		List<Conversation> conversations = getConversations();
341		for (int i = 0; i < conversations.size(); ++i) {
342			if ((conversations.get(i).getContactJid().equals(jid))) {
343				conversations.get(i).setContact(contact);
344				break;
345			}
346		}
347	}
348
349	public class XmppConnectionBinder extends Binder {
350		public XmppConnectionService getService() {
351			return XmppConnectionService.this;
352		}
353	}
354
355	@Override
356	public int onStartCommand(Intent intent, int flags, int startId) {
357		for (Account account : accounts) {
358			if (account.getXmppConnection() == null) {
359				if (!account.isOptionSet(Account.OPTION_DISABLED)) {
360					account.setXmppConnection(this.createConnection(account));
361				}
362			}
363		}
364		return START_STICKY;
365	}
366
367	@Override
368	public void onCreate() {
369		databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
370		this.accounts = databaseBackend.getAccounts();
371
372		getContentResolver().registerContentObserver(
373				ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
374		this.pgpServiceConnection = new OpenPgpServiceConnection(
375				getApplicationContext(), "org.sufficientlysecure.keychain");
376		this.pgpServiceConnection.bindToService();
377	}
378
379	@Override
380	public void onDestroy() {
381		super.onDestroy();
382		for (Account account : accounts) {
383			if (account.getXmppConnection() != null) {
384				disconnect(account);
385			}
386		}
387	}
388
389	public XmppConnection createConnection(Account account) {
390		PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
391		XmppConnection connection = new XmppConnection(account, pm);
392		connection.setOnMessagePacketReceivedListener(this.messageListener);
393		connection.setOnStatusChangedListener(this.statusListener);
394		connection.setOnPresencePacketReceivedListener(this.presenceListener);
395		connection
396				.setOnUnregisteredIqPacketReceivedListener(this.unknownIqListener);
397		Thread thread = new Thread(connection);
398		thread.start();
399		return connection;
400	}
401
402	public void sendMessage(Message message, String presence) {
403		Account account = message.getConversation().getAccount();
404		Conversation conv = message.getConversation();
405		boolean saveInDb = false;
406		boolean addToConversation = false;
407		if (account.getStatus() == Account.STATUS_ONLINE) {
408			MessagePacket packet;
409			if (message.getEncryption() == Message.ENCRYPTION_OTR) {
410				if (!conv.hasValidOtrSession()) {
411					// starting otr session. messages will be send later
412					conv.startOtrSession(getApplicationContext(), presence);
413				} else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
414					// otr session aleary exists, creating message packet
415					// accordingly
416					packet = prepareMessagePacket(account, message,
417							conv.getOtrSession());
418					account.getXmppConnection().sendMessagePacket(packet);
419					message.setStatus(Message.STATUS_SEND);
420				}
421				saveInDb = true;
422				addToConversation = true;
423			} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
424				long keyId = message.getConversation().getContact()
425						.getPgpKeyId();
426				packet = new MessagePacket();
427				packet.setType(MessagePacket.TYPE_CHAT);
428				packet.setFrom(message.getConversation().getAccount()
429						.getFullJid());
430				packet.setTo(message.getCounterpart());
431				packet.setBody("This is an XEP-0027 encryted message");
432				Element x = new Element("x");
433				x.setAttribute("xmlns", "jabber:x:encrypted");
434				x.setContent(this.getPgpEngine().encrypt(keyId,
435						message.getBody()));
436				packet.addChild(x);
437				account.getXmppConnection().sendMessagePacket(packet);
438				message.setStatus(Message.STATUS_SEND);
439				message.setEncryption(Message.ENCRYPTION_DECRYPTED);
440				saveInDb = true;
441				addToConversation = true;
442			} else {
443				// don't encrypt
444				if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
445					message.setStatus(Message.STATUS_SEND);
446					saveInDb = true;
447					addToConversation = true;
448				}
449
450				packet = prepareMessagePacket(account, message, null);
451				account.getXmppConnection().sendMessagePacket(packet);
452			}
453		} else {
454			// account is offline
455			saveInDb = true;
456			addToConversation = true;
457
458		}
459		if (saveInDb) {
460			databaseBackend.createMessage(message);
461		}
462		if (addToConversation) {
463			conv.getMessages().add(message);
464			if (convChangedListener != null) {
465				convChangedListener.onConversationListChanged();
466			}
467		}
468
469	}
470
471	private void sendUnsendMessages(Conversation conversation) {
472		for (int i = 0; i < conversation.getMessages().size(); ++i) {
473			if (conversation.getMessages().get(i).getStatus() == Message.STATUS_UNSEND) {
474				Message message = conversation.getMessages().get(i);
475				MessagePacket packet = prepareMessagePacket(
476						conversation.getAccount(), message, null);
477				conversation.getAccount().getXmppConnection()
478						.sendMessagePacket(packet);
479				message.setStatus(Message.STATUS_SEND);
480				if (conversation.getMode() == Conversation.MODE_SINGLE) {
481					databaseBackend.updateMessage(message);
482				} else {
483					databaseBackend.deleteMessage(message);
484					conversation.getMessages().remove(i);
485					i--;
486				}
487			}
488		}
489	}
490
491	public MessagePacket prepareMessagePacket(Account account, Message message,
492			Session otrSession) {
493		MessagePacket packet = new MessagePacket();
494		if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
495			packet.setType(MessagePacket.TYPE_CHAT);
496			if (otrSession != null) {
497				try {
498					packet.setBody(otrSession.transformSending(message
499							.getBody()));
500				} catch (OtrException e) {
501					Log.d(LOGTAG,
502							account.getJid()
503									+ ": could not encrypt message to "
504									+ message.getCounterpart());
505				}
506				Element privateMarker = new Element("private");
507				privateMarker.setAttribute("xmlns", "urn:xmpp:carbons:2");
508				packet.addChild(privateMarker);
509				packet.setTo(otrSession.getSessionID().getAccountID() + "/"
510						+ otrSession.getSessionID().getUserID());
511				packet.setFrom(account.getFullJid());
512			} else {
513				packet.setBody(message.getBody());
514				packet.setTo(message.getCounterpart());
515				packet.setFrom(account.getJid());
516			}
517		} else if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
518			packet.setType(MessagePacket.TYPE_GROUPCHAT);
519			packet.setBody(message.getBody());
520			packet.setTo(message.getCounterpart());
521			packet.setFrom(account.getJid());
522		}
523		return packet;
524	}
525
526	public void getRoster(Account account,
527			final OnRosterFetchedListener listener) {
528		List<Contact> contacts = databaseBackend.getContacts(account);
529		for (int i = 0; i < contacts.size(); ++i) {
530			contacts.get(i).setAccount(account);
531		}
532		if (listener != null) {
533			listener.onRosterFetched(contacts);
534		}
535	}
536
537	public void updateRoster(final Account account,
538			final OnRosterFetchedListener listener) {
539		IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
540		Element query = new Element("query");
541		query.setAttribute("xmlns", "jabber:iq:roster");
542		query.setAttribute("ver", account.getRosterVersion());
543		iqPacket.addChild(query);
544		account.getXmppConnection().sendIqPacket(iqPacket,
545				new OnIqPacketReceived() {
546
547					@Override
548					public void onIqPacketReceived(final Account account,
549							IqPacket packet) {
550						Element roster = packet.findChild("query");
551						if (roster != null) {
552							String version = roster.getAttribute("ver");
553							processRosterItems(account, roster);
554							StringBuilder mWhere = new StringBuilder();
555							mWhere.append("jid NOT IN(");
556							List<Element> items = roster.getChildren();
557							for (int i = 0; i < items.size(); ++i) {
558								mWhere.append(DatabaseUtils
559										.sqlEscapeString(items.get(i)
560												.getAttribute("jid")));
561								if (i != items.size() - 1) {
562									mWhere.append(",");
563								}
564							}
565							mWhere.append(") and accountUuid = \"");
566							mWhere.append(account.getUuid());
567							mWhere.append("\"");
568							List<Contact> contactsToDelete = databaseBackend
569									.getContats(mWhere.toString());
570							for (Contact contact : contactsToDelete) {
571								databaseBackend.deleteContact(contact);
572								replaceContactInConversation(contact.getJid(),
573										null);
574							}
575
576						}
577						mergePhoneContactsWithRoster(new OnPhoneContactsMerged() {
578
579							@Override
580							public void phoneContactsMerged() {
581								if (listener != null) {
582									getRoster(account, listener);
583								}
584							}
585						});
586					}
587				});
588	}
589
590	public void mergePhoneContactsWithRoster(
591			final OnPhoneContactsMerged listener) {
592		PhoneHelper.loadPhoneContacts(getApplicationContext(),
593				new OnPhoneContactsLoadedListener() {
594					@Override
595					public void onPhoneContactsLoaded(
596							Hashtable<String, Bundle> phoneContacts) {
597						List<Contact> contacts = databaseBackend
598								.getContacts(null);
599						for (int i = 0; i < contacts.size(); ++i) {
600							Contact contact = contacts.get(i);
601							if (phoneContacts.containsKey(contact.getJid())) {
602								Bundle phoneContact = phoneContacts.get(contact
603										.getJid());
604								String systemAccount = phoneContact
605										.getInt("phoneid")
606										+ "#"
607										+ phoneContact.getString("lookup");
608								contact.setSystemAccount(systemAccount);
609								contact.setPhotoUri(phoneContact
610										.getString("photouri"));
611								contact.setDisplayName(phoneContact
612										.getString("displayname"));
613								databaseBackend.updateContact(contact);
614								replaceContactInConversation(contact.getJid(),
615										contact);
616							} else {
617								if ((contact.getSystemAccount() != null)
618										|| (contact.getProfilePhoto() != null)) {
619									contact.setSystemAccount(null);
620									contact.setPhotoUri(null);
621									databaseBackend.updateContact(contact);
622									replaceContactInConversation(
623											contact.getJid(), contact);
624								}
625							}
626						}
627						if (listener != null) {
628							listener.phoneContactsMerged();
629						}
630					}
631				});
632	}
633
634	public List<Conversation> getConversations() {
635		if (this.conversations == null) {
636			Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
637			for (Account account : this.accounts) {
638				accountLookupTable.put(account.getUuid(), account);
639			}
640			this.conversations = databaseBackend
641					.getConversations(Conversation.STATUS_AVAILABLE);
642			for (Conversation conv : this.conversations) {
643				Account account = accountLookupTable.get(conv.getAccountUuid());
644				conv.setAccount(account);
645				conv.setContact(findContact(account, conv.getContactJid()));
646				conv.setMessages(databaseBackend.getMessages(conv, 50));
647			}
648		}
649		return this.conversations;
650	}
651
652	public List<Account> getAccounts() {
653		return this.accounts;
654	}
655
656	public Contact findContact(Account account, String jid) {
657		Contact contact = databaseBackend.findContact(account, jid);
658		if (contact != null) {
659			contact.setAccount(account);
660		}
661		return contact;
662	}
663
664	public Conversation findOrCreateConversation(Account account, String jid,
665			boolean muc) {
666		for (Conversation conv : this.getConversations()) {
667			if ((conv.getAccount().equals(account))
668					&& (conv.getContactJid().equals(jid))) {
669				return conv;
670			}
671		}
672		Conversation conversation = databaseBackend.findConversation(account,
673				jid);
674		if (conversation != null) {
675			conversation.setStatus(Conversation.STATUS_AVAILABLE);
676			conversation.setAccount(account);
677			if (muc) {
678				conversation.setMode(Conversation.MODE_MULTI);
679				if (account.getStatus() == Account.STATUS_ONLINE) {
680					joinMuc(conversation);
681				}
682			} else {
683				conversation.setMode(Conversation.MODE_SINGLE);
684			}
685			this.databaseBackend.updateConversation(conversation);
686			conversation.setContact(findContact(account,
687					conversation.getContactJid()));
688		} else {
689			String conversationName;
690			Contact contact = findContact(account, jid);
691			if (contact != null) {
692				conversationName = contact.getDisplayName();
693			} else {
694				conversationName = jid.split("@")[0];
695			}
696			if (muc) {
697				conversation = new Conversation(conversationName, account, jid,
698						Conversation.MODE_MULTI);
699				if (account.getStatus() == Account.STATUS_ONLINE) {
700					joinMuc(conversation);
701				}
702			} else {
703				conversation = new Conversation(conversationName, account, jid,
704						Conversation.MODE_SINGLE);
705			}
706			conversation.setContact(contact);
707			this.databaseBackend.createConversation(conversation);
708		}
709		this.conversations.add(conversation);
710		if (this.convChangedListener != null) {
711			this.convChangedListener.onConversationListChanged();
712		}
713		return conversation;
714	}
715
716	public void archiveConversation(Conversation conversation) {
717		if (conversation.getMode() == Conversation.MODE_MULTI) {
718			leaveMuc(conversation);
719		} else {
720			try {
721				conversation.endOtrIfNeeded();
722			} catch (OtrException e) {
723				Log.d(LOGTAG,
724						"error ending otr session for "
725								+ conversation.getName());
726			}
727		}
728		this.databaseBackend.updateConversation(conversation);
729		this.conversations.remove(conversation);
730		if (this.convChangedListener != null) {
731			this.convChangedListener.onConversationListChanged();
732		}
733	}
734
735	public int getConversationCount() {
736		return this.databaseBackend.getConversationCount();
737	}
738
739	public void createAccount(Account account) {
740		databaseBackend.createAccount(account);
741		this.accounts.add(account);
742		account.setXmppConnection(this.createConnection(account));
743		if (accountChangedListener != null)
744			accountChangedListener.onAccountListChangedListener();
745	}
746
747	public void deleteContact(Contact contact) {
748		IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
749		Element query = new Element("query");
750		query.setAttribute("xmlns", "jabber:iq:roster");
751		Element item = new Element("item");
752		item.setAttribute("jid", contact.getJid());
753		item.setAttribute("subscription", "remove");
754		query.addChild(item);
755		iq.addChild(query);
756		contact.getAccount().getXmppConnection().sendIqPacket(iq, null);
757		replaceContactInConversation(contact.getJid(), null);
758		databaseBackend.deleteContact(contact);
759	}
760
761	public void updateAccount(Account account) {
762		databaseBackend.updateAccount(account);
763		if (account.getXmppConnection() != null) {
764			disconnect(account);
765		}
766		if (!account.isOptionSet(Account.OPTION_DISABLED)) {
767			account.setXmppConnection(this.createConnection(account));
768		}
769		if (accountChangedListener != null)
770			accountChangedListener.onAccountListChangedListener();
771	}
772
773	public void deleteAccount(Account account) {
774		Log.d(LOGTAG, "called delete account");
775		if (account.getXmppConnection() != null) {
776			this.disconnect(account);
777		}
778		databaseBackend.deleteAccount(account);
779		this.accounts.remove(account);
780		if (accountChangedListener != null)
781			accountChangedListener.onAccountListChangedListener();
782	}
783
784	public void setOnConversationListChangedListener(
785			OnConversationListChangedListener listener) {
786		this.convChangedListener = listener;
787	}
788
789	public void removeOnConversationListChangedListener() {
790		this.convChangedListener = null;
791	}
792
793	public void setOnAccountListChangedListener(
794			OnAccountListChangedListener listener) {
795		this.accountChangedListener = listener;
796	}
797
798	public void removeOnAccountListChangedListener() {
799		this.accountChangedListener = null;
800	}
801
802	public void connectMultiModeConversations(Account account) {
803		List<Conversation> conversations = getConversations();
804		for (int i = 0; i < conversations.size(); i++) {
805			Conversation conversation = conversations.get(i);
806			if ((conversation.getMode() == Conversation.MODE_MULTI)
807					&& (conversation.getAccount() == account)) {
808				joinMuc(conversation);
809			}
810		}
811	}
812
813	public void joinMuc(Conversation conversation) {
814		String muc = conversation.getContactJid();
815		PresencePacket packet = new PresencePacket();
816		packet.setAttribute("to", muc + "/"
817				+ conversation.getAccount().getUsername());
818		Element x = new Element("x");
819		x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
820		if (conversation.getMessages().size() != 0) {
821			Element history = new Element("history");
822			long lastMsgTime = conversation.getLatestMessage().getTimeSent();
823			long diff = (System.currentTimeMillis() - lastMsgTime) / 1000 - 1;
824			history.setAttribute("seconds", diff + "");
825			x.addChild(history);
826		}
827		packet.addChild(x);
828		conversation.getAccount().getXmppConnection()
829				.sendPresencePacket(packet);
830	}
831
832	public void leaveMuc(Conversation conversation) {
833
834	}
835
836	public void disconnect(Account account) {
837		List<Conversation> conversations = getConversations();
838		for (int i = 0; i < conversations.size(); i++) {
839			Conversation conversation = conversations.get(i);
840			if (conversation.getAccount() == account) {
841				if (conversation.getMode() == Conversation.MODE_MULTI) {
842					leaveMuc(conversation);
843				} else {
844					try {
845						conversation.endOtrIfNeeded();
846					} catch (OtrException e) {
847						Log.d(LOGTAG, "error ending otr session for "
848								+ conversation.getName());
849					}
850				}
851			}
852		}
853		account.getXmppConnection().disconnect();
854		Log.d(LOGTAG, "disconnected account: " + account.getJid());
855		account.setXmppConnection(null);
856	}
857
858	@Override
859	public IBinder onBind(Intent intent) {
860		return mBinder;
861	}
862
863	public void updateContact(Contact contact) {
864		databaseBackend.updateContact(contact);
865	}
866
867	public void updateMessage(Message message) {
868		databaseBackend.updateMessage(message);
869	}
870
871	public void createContact(Contact contact) {
872		SharedPreferences sharedPref = PreferenceManager
873				.getDefaultSharedPreferences(getApplicationContext());
874		boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
875		if (autoGrant) {
876			contact.setSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
877			contact.setSubscriptionOption(Contact.Subscription.ASKING);
878		}
879		databaseBackend.createContact(contact);
880		IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
881		Element query = new Element("query");
882		query.setAttribute("xmlns", "jabber:iq:roster");
883		Element item = new Element("item");
884		item.setAttribute("jid", contact.getJid());
885		item.setAttribute("name", contact.getJid());
886		query.addChild(item);
887		iq.addChild(query);
888		Account account = contact.getAccount();
889		account.getXmppConnection().sendIqPacket(iq, null);
890		if (autoGrant) {
891			requestPresenceUpdatesFrom(contact);
892		}
893		replaceContactInConversation(contact.getJid(), contact);
894	}
895
896	public void requestPresenceUpdatesFrom(Contact contact) {
897		// Requesting a Subscription type=subscribe
898		PresencePacket packet = new PresencePacket();
899		packet.setAttribute("type", "subscribe");
900		packet.setAttribute("to", contact.getJid());
901		packet.setAttribute("from", contact.getAccount().getJid());
902		Log.d(LOGTAG, packet.toString());
903		contact.getAccount().getXmppConnection().sendPresencePacket(packet);
904	}
905
906	public void stopPresenceUpdatesFrom(Contact contact) {
907		// Unsubscribing type='unsubscribe'
908		PresencePacket packet = new PresencePacket();
909		packet.setAttribute("type", "unsubscribe");
910		packet.setAttribute("to", contact.getJid());
911		packet.setAttribute("from", contact.getAccount().getJid());
912		Log.d(LOGTAG, packet.toString());
913		contact.getAccount().getXmppConnection().sendPresencePacket(packet);
914	}
915
916	public void stopPresenceUpdatesTo(Contact contact) {
917		// Canceling a Subscription type=unsubscribed
918		PresencePacket packet = new PresencePacket();
919		packet.setAttribute("type", "unsubscribed");
920		packet.setAttribute("to", contact.getJid());
921		packet.setAttribute("from", contact.getAccount().getJid());
922		Log.d(LOGTAG, packet.toString());
923		contact.getAccount().getXmppConnection().sendPresencePacket(packet);
924	}
925
926	public void sendPresenceUpdatesTo(Contact contact) {
927		// type='subscribed'
928		PresencePacket packet = new PresencePacket();
929		packet.setAttribute("type", "subscribed");
930		packet.setAttribute("to", contact.getJid());
931		packet.setAttribute("from", contact.getAccount().getJid());
932		Log.d(LOGTAG, packet.toString());
933		contact.getAccount().getXmppConnection().sendPresencePacket(packet);
934	}
935	
936	public void sendPgpPresence(Account account, String signature) {
937		PresencePacket packet = new PresencePacket();
938		packet.setAttribute("from", account.getFullJid());
939		Element status = new Element("status");
940		status.setContent("online");
941		packet.addChild(status);
942		Element x = new Element("x");
943		x.setAttribute("xmlns", "jabber:x:signed");
944		x.setContent(signature);
945		packet.addChild(x);
946		account.getXmppConnection().sendPresencePacket(packet);
947	}
948
949	public void generatePgpAnnouncement(Account account)
950			throws PgpEngine.UserInputRequiredException {
951		if (account.getStatus() == Account.STATUS_ONLINE) {
952			String signature = getPgpEngine().generateSignature("online");
953			account.setKey("pgp_signature", signature);
954			databaseBackend.updateAccount(account);
955			sendPgpPresence(account, signature);
956		}
957	}
958}