XmppConnectionService.java

  1package de.gultsch.chat.services;
  2
  3import java.util.ArrayList;
  4import java.util.Hashtable;
  5import java.util.List;
  6
  7import de.gultsch.chat.entities.Account;
  8import de.gultsch.chat.entities.Contact;
  9import de.gultsch.chat.entities.Conversation;
 10import de.gultsch.chat.entities.Message;
 11import de.gultsch.chat.persistance.DatabaseBackend;
 12import de.gultsch.chat.ui.OnAccountListChangedListener;
 13import de.gultsch.chat.ui.OnConversationListChangedListener;
 14import de.gultsch.chat.ui.OnRosterFetchedListener;
 15import de.gultsch.chat.utils.UIHelper;
 16import de.gultsch.chat.xml.Element;
 17import de.gultsch.chat.xmpp.IqPacket;
 18import de.gultsch.chat.xmpp.MessagePacket;
 19import de.gultsch.chat.xmpp.OnIqPacketReceived;
 20import de.gultsch.chat.xmpp.OnMessagePacketReceived;
 21import de.gultsch.chat.xmpp.OnPresencePacketReceived;
 22import de.gultsch.chat.xmpp.OnStatusChanged;
 23import de.gultsch.chat.xmpp.PresencePacket;
 24import de.gultsch.chat.xmpp.XmppConnection;
 25import android.app.NotificationManager;
 26import android.app.Service;
 27import android.content.Context;
 28import android.content.CursorLoader;
 29import android.content.Intent;
 30import android.content.Loader;
 31import android.content.Loader.OnLoadCompleteListener;
 32import android.database.Cursor;
 33import android.os.Binder;
 34import android.os.Bundle;
 35import android.os.IBinder;
 36import android.os.PowerManager;
 37import android.provider.ContactsContract;
 38import android.util.Log;
 39
 40public class XmppConnectionService extends Service {
 41
 42	protected static final String LOGTAG = "xmppService";
 43	protected DatabaseBackend databaseBackend;
 44
 45	public long startDate;
 46
 47	private List<Account> accounts;
 48	private List<Conversation> conversations = null;
 49
 50	private Hashtable<Account, XmppConnection> connections = new Hashtable<Account, XmppConnection>();
 51
 52	private OnConversationListChangedListener convChangedListener = null;
 53	private OnAccountListChangedListener accountChangedListener = null;
 54
 55	private final IBinder mBinder = new XmppConnectionBinder();
 56	private OnMessagePacketReceived messageListener = new OnMessagePacketReceived() {
 57
 58		@Override
 59		public void onMessagePacketReceived(Account account,
 60				MessagePacket packet) {
 61			Conversation conversation = null;
 62			String fullJid = packet.getFrom();
 63			String counterPart = null;
 64			if (packet.getType() == MessagePacket.TYPE_CHAT) {
 65				String jid = fullJid.split("/")[0];
 66				counterPart = fullJid;
 67				Contact contact = findOrCreateContact(account,jid);
 68				conversation = findOrCreateConversation(account, contact,false);
 69			} else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
 70				String[] fromParts = fullJid.split("/");
 71				if (fromParts.length != 2) {
 72					return;
 73				}
 74				if (packet.hasChild("subject")) {
 75					return;
 76				}
 77				if (packet.hasChild("delay")) {
 78					return;
 79				}
 80
 81				String muc = fromParts[0];
 82				counterPart = fromParts[1];
 83				if (counterPart.equals(account.getUsername())) {
 84					return;
 85				}
 86				for (int i = 0; i < conversations.size(); ++i) {
 87					if (conversations.get(i).getContactJid().equals(muc)) {
 88						conversation = conversations.get(i);
 89						break;
 90					}
 91				}
 92				if (conversation == null) {
 93					Log.d(LOGTAG, "couldnt find muc");
 94				}
 95
 96			}
 97			if (conversation != null) {
 98				Log.d(LOGTAG, packet.toString());
 99				Message message = new Message(conversation, counterPart,
100						packet.getBody(), Message.ENCRYPTION_NONE,
101						Message.STATUS_RECIEVED);
102				conversation.getMessages().add(message);
103				databaseBackend.createMessage(message);
104				if (convChangedListener != null) {
105					convChangedListener.onConversationListChanged();
106				} else {
107					NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
108					mNotificationManager.notify(2342, UIHelper
109							.getUnreadMessageNotification(
110									getApplicationContext(), conversation));
111				}
112			}
113		}
114	};
115	private OnStatusChanged statusListener = new OnStatusChanged() {
116
117		@Override
118		public void onStatusChanged(Account account) {
119			if (accountChangedListener != null) {
120				accountChangedListener.onAccountListChangedListener();
121			}
122			if (account.getStatus() == Account.STATUS_ONLINE) {
123				connectMultiModeConversations(account);
124			}
125		}
126	};
127	
128	private OnPresencePacketReceived presenceListener = new OnPresencePacketReceived() {
129		
130		@Override
131		public void onPresencePacketReceived(Account account, PresencePacket packet) {
132			String jid = packet.getAttribute("from");
133			String type = packet.getAttribute("type");
134			if (type==null) {
135				//Log.d(LOGTAG,"online presence from "+jid);
136			}
137		}
138	};
139
140	public class XmppConnectionBinder extends Binder {
141		public XmppConnectionService getService() {
142			return XmppConnectionService.this;
143		}
144	}
145
146	@Override
147	public int onStartCommand(Intent intent, int flags, int startId) {
148		for (Account account : accounts) {
149			if (!connections.containsKey(account)) {
150				if (!account.isOptionSet(Account.OPTION_DISABLED)) {
151					this.connections.put(account,
152							this.createConnection(account));
153				} else {
154					Log.d(LOGTAG, account.getJid()
155							+ ": not starting because it's disabled");
156				}
157			}
158		}
159		return START_STICKY;
160	}
161
162	@Override
163	public void onCreate() {
164		databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
165		this.accounts = databaseBackend.getAccounts();
166	}
167
168	public XmppConnection createConnection(Account account) {
169		PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
170		XmppConnection connection = new XmppConnection(account, pm);
171		connection.setOnMessagePacketReceivedListener(this.messageListener);
172		connection.setOnStatusChangedListener(this.statusListener);
173		connection.setOnPresencePacketReceivedListener(this.presenceListener);
174		Thread thread = new Thread(connection);
175		thread.start();
176		return connection;
177	}
178
179	@Override
180	public IBinder onBind(Intent intent) {
181		return mBinder;
182	}
183
184	public void sendMessage(final Account account, final Message message) {
185		Log.d(LOGTAG, "sending message for " + account.getJid() + " to: "
186				+ message.getCounterpart());
187		databaseBackend.createMessage(message);
188		MessagePacket packet = new MessagePacket();
189		if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
190			packet.setType(MessagePacket.TYPE_CHAT);
191		} else if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
192			packet.setType(MessagePacket.TYPE_GROUPCHAT);
193		}
194		packet.setTo(message.getCounterpart());
195		packet.setFrom(account.getJid());
196		packet.setBody(message.getBody());
197		connections.get(account).sendMessagePacket(packet);
198		message.setStatus(Message.STATUS_SEND);
199		databaseBackend.updateMessage(message);
200	}
201	
202	public void getRoster(Account account, final OnRosterFetchedListener listener) {
203		List<Contact> contacts = databaseBackend.getContacts(account);
204		for(int i=0; i < contacts.size(); ++i) {
205			contacts.get(i).setAccount(account);
206		}
207		if (listener != null) {
208			listener.onRosterFetched(contacts);
209		}
210	}
211
212	public void updateRoster(final Account account,
213			final OnRosterFetchedListener listener) {
214
215		final Hashtable<String, Bundle> phoneContacts = new Hashtable<String, Bundle>();
216		final List<Contact> contacts = new ArrayList<Contact>();
217
218		final String[] PROJECTION = new String[] {
219				ContactsContract.Data.CONTACT_ID,
220				ContactsContract.Data.DISPLAY_NAME,
221				ContactsContract.Data.PHOTO_THUMBNAIL_URI,
222				ContactsContract.CommonDataKinds.Im.DATA };
223
224		final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\""
225				+ ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
226				+ "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL
227				+ "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER
228				+ "\")";
229
230		CursorLoader mCursorLoader = new CursorLoader(this,
231				ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null,
232				null);
233		mCursorLoader.registerListener(0, new OnLoadCompleteListener<Cursor>() {
234
235			@Override
236			public void onLoadComplete(Loader<Cursor> arg0, Cursor cursor) {
237				while (cursor.moveToNext()) {
238					Bundle contact = new Bundle();
239					contact.putInt("phoneid", cursor.getInt(cursor
240							.getColumnIndex(ContactsContract.Data.CONTACT_ID)));
241					contact.putString(
242							"displayname",
243							cursor.getString(cursor
244									.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)));
245					contact.putString(
246							"photouri",
247							cursor.getString(cursor
248									.getColumnIndex(ContactsContract.Data.PHOTO_THUMBNAIL_URI)));
249					phoneContacts.put(
250							cursor.getString(cursor
251									.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)),
252							contact);
253				}
254				IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
255				Element query = new Element("query");
256				query.setAttribute("xmlns", "jabber:iq:roster");
257				query.setAttribute("ver", "");
258				iqPacket.addChild(query);
259				connections.get(account).sendIqPacket(iqPacket,
260						new OnIqPacketReceived() {
261
262							@Override
263							public void onIqPacketReceived(Account account,
264									IqPacket packet) {
265								Element roster = packet.findChild("query");
266								if (roster != null) {
267									for (Element item : roster.getChildren()) {
268										Contact contact;
269										String name = item.getAttribute("name");
270										String jid = item.getAttribute("jid");
271										if (phoneContacts.containsKey(jid)) {
272											Bundle phoneContact = phoneContacts
273													.get(jid);
274											contact = new Contact(
275													account,
276													phoneContact
277															.getString("displayname"),
278													jid,
279													phoneContact
280															.getString("photouri"));
281											contact.setSystemAccount(phoneContact.getInt("phoneid"));
282										} else {
283											if (name == null) {
284												name = jid.split("@")[0];
285											}
286											contact = new Contact(account,
287													name, jid, null);
288
289										}
290										contact.setAccount(account);
291										contact.setSubscription(item
292												.getAttribute("subscription"));
293										contacts.add(contact);
294									}
295									databaseBackend.mergeContacts(contacts);
296									if (listener != null) {
297										listener.onRosterFetched(contacts);
298									}
299								}
300							}
301						});
302
303			}
304		});
305		mCursorLoader.startLoading();
306	}
307
308	public void addConversation(Conversation conversation) {
309		databaseBackend.createConversation(conversation);
310	}
311
312	public List<Conversation> getConversations() {
313		if (this.conversations == null) {
314			Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
315			for (Account account : this.accounts) {
316				accountLookupTable.put(account.getUuid(), account);
317			}
318			this.conversations = databaseBackend
319					.getConversations(Conversation.STATUS_AVAILABLE);
320			for (Conversation conv : this.conversations) {
321				conv.setAccount(accountLookupTable.get(conv.getAccountUuid()));
322			}
323		}
324		return this.conversations;
325	}
326
327	public List<Account> getAccounts() {
328		return this.accounts;
329	}
330
331	public List<Message> getMessages(Conversation conversation) {
332		return databaseBackend.getMessages(conversation, 100);
333	}
334	
335	public Contact findOrCreateContact(Account account, String jid) {
336		Contact contact = databaseBackend.findContact(account,jid);
337		if (contact!=null) {
338			return contact;
339		} else {
340			return new Contact(account,jid.split("@")[0], jid, null);
341		}
342	}
343
344	public Conversation findOrCreateConversation(Account account,
345			Contact contact,boolean muc) {
346		for (Conversation conv : this.getConversations()) {
347			if ((conv.getAccount().equals(account))
348					&& (conv.getContactJid().equals(contact.getJid()))) {
349				return conv;
350			}
351		}
352		Conversation conversation = databaseBackend.findConversation(account,
353				contact.getJid());
354		if (conversation != null) {
355			conversation.setStatus(Conversation.STATUS_AVAILABLE);
356			conversation.setAccount(account);
357			if (muc) {
358				conversation.setMode(Conversation.MODE_MULTI);
359				if (account.getStatus()==Account.STATUS_ONLINE) {
360					joinMuc(account, conversation);
361				}
362			} else {
363				conversation.setMode(Conversation.MODE_SINGLE);
364			}
365			this.databaseBackend.updateConversation(conversation);
366		} else {
367			if (muc) {
368				conversation = new Conversation(contact.getDisplayName(),
369						contact.getProfilePhoto(), account, contact.getJid(),
370						Conversation.MODE_MULTI);
371				if (account.getStatus()==Account.STATUS_ONLINE) {
372					joinMuc(account, conversation);
373				}
374			} else {
375				conversation = new Conversation(contact.getDisplayName(),
376						contact.getProfilePhoto(), account, contact.getJid(),
377						Conversation.MODE_SINGLE);
378			}
379			this.databaseBackend.createConversation(conversation);
380		}
381		this.conversations.add(conversation);
382		if (this.convChangedListener != null) {
383			this.convChangedListener.onConversationListChanged();
384		}
385		return conversation;
386	}
387
388	public void archiveConversation(Conversation conversation) {
389		this.databaseBackend.updateConversation(conversation);
390		this.conversations.remove(conversation);
391		if (this.convChangedListener != null) {
392			this.convChangedListener.onConversationListChanged();
393		}
394	}
395
396	public int getConversationCount() {
397		return this.databaseBackend.getConversationCount();
398	}
399
400	public void createAccount(Account account) {
401		databaseBackend.createAccount(account);
402		this.accounts.add(account);
403		this.connections.put(account, this.createConnection(account));
404		if (accountChangedListener != null)
405			accountChangedListener.onAccountListChangedListener();
406	}
407
408	public void updateAccount(Account account) {
409		databaseBackend.updateAccount(account);
410		XmppConnection connection = this.connections.get(account);
411		if (connection != null) {
412			connection.disconnect();
413			this.connections.remove(account);
414		}
415		if (!account.isOptionSet(Account.OPTION_DISABLED)) {
416			this.connections.put(account, this.createConnection(account));
417		} else {
418			Log.d(LOGTAG, account.getJid()
419					+ ": not starting because it's disabled");
420		}
421		if (accountChangedListener != null)
422			accountChangedListener.onAccountListChangedListener();
423	}
424
425	public void deleteAccount(Account account) {
426		Log.d(LOGTAG, "called delete account");
427		if (this.connections.containsKey(account)) {
428			Log.d(LOGTAG, "found connection. disconnecting");
429			this.connections.get(account).disconnect();
430			this.connections.remove(account);
431			this.accounts.remove(account);
432		}
433		databaseBackend.deleteAccount(account);
434		if (accountChangedListener != null)
435			accountChangedListener.onAccountListChangedListener();
436	}
437
438	public void setOnConversationListChangedListener(
439			OnConversationListChangedListener listener) {
440		this.convChangedListener = listener;
441	}
442
443	public void removeOnConversationListChangedListener() {
444		this.convChangedListener = null;
445	}
446
447	public void setOnAccountListChangedListener(
448			OnAccountListChangedListener listener) {
449		this.accountChangedListener = listener;
450	}
451
452	public void removeOnAccountListChangedListener() {
453		this.accountChangedListener = null;
454	}
455
456	public void connectMultiModeConversations(Account account) {
457		List<Conversation> conversations = getConversations();
458		for (int i = 0; i < conversations.size(); i++) {
459			Conversation conversation = conversations.get(i);
460			if ((conversation.getMode() == Conversation.MODE_MULTI)
461					&& (conversation.getAccount() == account)) {
462				joinMuc(account, conversation);
463			}
464		}
465	}
466	
467	public void joinMuc(Account account, Conversation conversation) {
468		String muc = conversation.getContactJid();
469		PresencePacket packet = new PresencePacket();
470		packet.setAttribute("to", muc + "/" + account.getUsername());
471		Element x = new Element("x");
472		x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
473		packet.addChild(x);
474		connections.get(conversation.getAccount()).sendPresencePacket(
475				packet);
476	}
477
478	public void disconnectMultiModeConversations() {
479
480	}
481}