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);
 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		if (listener != null) {
205			listener.onRosterFetched(contacts);
206		}
207	}
208
209	public void updateRoster(final Account account,
210			final OnRosterFetchedListener listener) {
211
212		final Hashtable<String, Bundle> phoneContacts = new Hashtable<String, Bundle>();
213		final List<Contact> contacts = new ArrayList<Contact>();
214
215		final String[] PROJECTION = new String[] {
216				ContactsContract.Data.CONTACT_ID,
217				ContactsContract.Data.DISPLAY_NAME,
218				ContactsContract.Data.PHOTO_THUMBNAIL_URI,
219				ContactsContract.CommonDataKinds.Im.DATA };
220
221		final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\""
222				+ ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
223				+ "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL
224				+ "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER
225				+ "\")";
226
227		CursorLoader mCursorLoader = new CursorLoader(this,
228				ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null,
229				null);
230		mCursorLoader.registerListener(0, new OnLoadCompleteListener<Cursor>() {
231
232			@Override
233			public void onLoadComplete(Loader<Cursor> arg0, Cursor cursor) {
234				while (cursor.moveToNext()) {
235					Bundle contact = new Bundle();
236					contact.putInt("phoneid", cursor.getInt(cursor
237							.getColumnIndex(ContactsContract.Data.CONTACT_ID)));
238					contact.putString(
239							"displayname",
240							cursor.getString(cursor
241									.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)));
242					contact.putString(
243							"photouri",
244							cursor.getString(cursor
245									.getColumnIndex(ContactsContract.Data.PHOTO_THUMBNAIL_URI)));
246					phoneContacts.put(
247							cursor.getString(cursor
248									.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)),
249							contact);
250				}
251				IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
252				Element query = new Element("query");
253				query.setAttribute("xmlns", "jabber:iq:roster");
254				query.setAttribute("ver", "");
255				iqPacket.addChild(query);
256				connections.get(account).sendIqPacket(iqPacket,
257						new OnIqPacketReceived() {
258
259							@Override
260							public void onIqPacketReceived(Account account,
261									IqPacket packet) {
262								Element roster = packet.findChild("query");
263								if (roster != null) {
264									for (Element item : roster.getChildren()) {
265										Contact contact;
266										String name = item.getAttribute("name");
267										String jid = item.getAttribute("jid");
268										if (phoneContacts.containsKey(jid)) {
269											Bundle phoneContact = phoneContacts
270													.get(jid);
271											contact = new Contact(
272													account,
273													phoneContact
274															.getString("displayname"),
275													jid,
276													phoneContact
277															.getString("photouri"));
278											contact.setSystemAccount(phoneContact.getInt("phoneid"));
279										} else {
280											if (name == null) {
281												name = jid.split("@")[0];
282											}
283											contact = new Contact(account,
284													name, jid, null);
285
286										}
287										contact.setAccount(account);
288										contact.setSubscription(item
289												.getAttribute("subscription"));
290										contacts.add(contact);
291									}
292									databaseBackend.mergeContacts(contacts);
293									if (listener != null) {
294										listener.onRosterFetched(contacts);
295									}
296								}
297							}
298						});
299
300			}
301		});
302		mCursorLoader.startLoading();
303	}
304
305	public void addConversation(Conversation conversation) {
306		databaseBackend.createConversation(conversation);
307	}
308
309	public List<Conversation> getConversations() {
310		if (this.conversations == null) {
311			Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
312			for (Account account : this.accounts) {
313				accountLookupTable.put(account.getUuid(), account);
314			}
315			this.conversations = databaseBackend
316					.getConversations(Conversation.STATUS_AVAILABLE);
317			for (Conversation conv : this.conversations) {
318				conv.setAccount(accountLookupTable.get(conv.getAccountUuid()));
319			}
320		}
321		return this.conversations;
322	}
323
324	public List<Account> getAccounts() {
325		return this.accounts;
326	}
327
328	public List<Message> getMessages(Conversation conversation) {
329		return databaseBackend.getMessages(conversation, 100);
330	}
331	
332	public Contact findOrCreateContact(Account account, String jid) {
333		Contact contact = databaseBackend.findContact(account,jid);
334		if (contact!=null) {
335			return contact;
336		} else {
337			return new Contact(account,jid.split("@")[0], jid, null);
338		}
339	}
340
341	public Conversation findOrCreateConversation(Account account,
342			Contact contact) {
343		// Log.d(LOGTAG,"was asked to find conversation for "+contact.getJid());
344		for (Conversation conv : this.getConversations()) {
345			if ((conv.getAccount().equals(account))
346					&& (conv.getContactJid().equals(contact.getJid()))) {
347				// Log.d(LOGTAG,"found one in memory");
348				return conv;
349			}
350		}
351		Conversation conversation = databaseBackend.findConversation(account,
352				contact.getJid());
353		if (conversation != null) {
354			Log.d("gultsch", "found one. unarchive it");
355			conversation.setStatus(Conversation.STATUS_AVAILABLE);
356			conversation.setAccount(account);
357			this.databaseBackend.updateConversation(conversation);
358		} else {
359			Log.d(LOGTAG, "didnt find one in archive. create new one");
360			conversation = new Conversation(contact.getDisplayName(),
361					contact.getProfilePhoto(), account, contact.getJid(),
362					Conversation.MODE_SINGLE);
363			this.databaseBackend.createConversation(conversation);
364		}
365		this.conversations.add(conversation);
366		if (this.convChangedListener != null) {
367			this.convChangedListener.onConversationListChanged();
368		}
369		return conversation;
370	}
371
372	public void archiveConversation(Conversation conversation) {
373		this.databaseBackend.updateConversation(conversation);
374		this.conversations.remove(conversation);
375		if (this.convChangedListener != null) {
376			this.convChangedListener.onConversationListChanged();
377		}
378	}
379
380	public int getConversationCount() {
381		return this.databaseBackend.getConversationCount();
382	}
383
384	public void createAccount(Account account) {
385		databaseBackend.createAccount(account);
386		this.accounts.add(account);
387		this.connections.put(account, this.createConnection(account));
388		if (accountChangedListener != null)
389			accountChangedListener.onAccountListChangedListener();
390	}
391
392	public void updateAccount(Account account) {
393		databaseBackend.updateAccount(account);
394		XmppConnection connection = this.connections.get(account);
395		if (connection != null) {
396			connection.disconnect();
397			this.connections.remove(account);
398		}
399		if (!account.isOptionSet(Account.OPTION_DISABLED)) {
400			this.connections.put(account, this.createConnection(account));
401		} else {
402			Log.d(LOGTAG, account.getJid()
403					+ ": not starting because it's disabled");
404		}
405		if (accountChangedListener != null)
406			accountChangedListener.onAccountListChangedListener();
407	}
408
409	public void deleteAccount(Account account) {
410		Log.d(LOGTAG, "called delete account");
411		if (this.connections.containsKey(account)) {
412			Log.d(LOGTAG, "found connection. disconnecting");
413			this.connections.get(account).disconnect();
414			this.connections.remove(account);
415			this.accounts.remove(account);
416		}
417		databaseBackend.deleteAccount(account);
418		if (accountChangedListener != null)
419			accountChangedListener.onAccountListChangedListener();
420	}
421
422	public void setOnConversationListChangedListener(
423			OnConversationListChangedListener listener) {
424		this.convChangedListener = listener;
425	}
426
427	public void removeOnConversationListChangedListener() {
428		this.convChangedListener = null;
429	}
430
431	public void setOnAccountListChangedListener(
432			OnAccountListChangedListener listener) {
433		this.accountChangedListener = listener;
434	}
435
436	public void removeOnAccountListChangedListener() {
437		this.accountChangedListener = null;
438	}
439
440	public void connectMultiModeConversations(Account account) {
441		List<Conversation> conversations = getConversations();
442		for (int i = 0; i < conversations.size(); i++) {
443			Conversation conversation = conversations.get(i);
444			if ((conversation.getMode() == Conversation.MODE_MULTI)
445					&& (conversation.getAccount() == account)) {
446				String muc = conversation.getContactJid();
447				Log.d(LOGTAG,
448						"join muc " + muc + " with account " + account.getJid());
449				PresencePacket packet = new PresencePacket();
450				packet.setAttribute("to", muc + "/" + account.getUsername());
451				Element x = new Element("x");
452				x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
453				packet.addChild(x);
454				connections.get(conversation.getAccount()).sendPresencePacket(
455						packet);
456
457			}
458		}
459	}
460
461	public void disconnectMultiModeConversations() {
462
463	}
464}