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}