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