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}