XmppConnectionService.java

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