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}