XmppConnectionService.java

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