XmppConnectionService.java

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