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