1package de.gultsch.chat.services;
2
3import java.text.ParseException;
4import java.text.SimpleDateFormat;
5import java.util.ArrayList;
6import java.util.Date;
7import java.util.Hashtable;
8import java.util.List;
9import java.util.Set;
10
11import net.java.otr4j.OtrException;
12import net.java.otr4j.session.Session;
13import net.java.otr4j.session.SessionImpl;
14import net.java.otr4j.session.SessionStatus;
15
16import de.gultsch.chat.entities.Account;
17import de.gultsch.chat.entities.Contact;
18import de.gultsch.chat.entities.Conversation;
19import de.gultsch.chat.entities.Message;
20import de.gultsch.chat.entities.Presences;
21import de.gultsch.chat.persistance.DatabaseBackend;
22import de.gultsch.chat.persistance.OnPhoneContactsMerged;
23import de.gultsch.chat.ui.OnAccountListChangedListener;
24import de.gultsch.chat.ui.OnConversationListChangedListener;
25import de.gultsch.chat.ui.OnRosterFetchedListener;
26import de.gultsch.chat.utils.MessageParser;
27import de.gultsch.chat.utils.OnPhoneContactsLoadedListener;
28import de.gultsch.chat.utils.PhoneHelper;
29import de.gultsch.chat.utils.UIHelper;
30import de.gultsch.chat.xml.Element;
31import de.gultsch.chat.xmpp.IqPacket;
32import de.gultsch.chat.xmpp.MessagePacket;
33import de.gultsch.chat.xmpp.OnIqPacketReceived;
34import de.gultsch.chat.xmpp.OnMessagePacketReceived;
35import de.gultsch.chat.xmpp.OnPresencePacketReceived;
36import de.gultsch.chat.xmpp.OnStatusChanged;
37import de.gultsch.chat.xmpp.PresencePacket;
38import de.gultsch.chat.xmpp.XmppConnection;
39import android.app.NotificationManager;
40import android.app.Service;
41import android.content.Context;
42import android.content.Intent;
43import android.database.ContentObserver;
44import android.database.DatabaseUtils;
45import android.os.Binder;
46import android.os.Bundle;
47import android.os.IBinder;
48import android.os.PowerManager;
49import android.provider.ContactsContract;
50import android.util.Log;
51
52public class XmppConnectionService extends Service {
53
54 protected static final String LOGTAG = "xmppService";
55 public DatabaseBackend databaseBackend;
56
57 public long startDate;
58
59 private List<Account> accounts;
60 private List<Conversation> conversations = null;
61
62 public OnConversationListChangedListener convChangedListener = null;
63 private OnAccountListChangedListener accountChangedListener = null;
64
65 private ContentObserver contactObserver = new ContentObserver(null) {
66 @Override
67 public void onChange(boolean selfChange) {
68 super.onChange(selfChange);
69 Log.d(LOGTAG, "contact list has changed");
70 mergePhoneContactsWithRoster(null);
71 }
72 };
73
74 private XmppConnectionService service = this;
75
76 private final IBinder mBinder = new XmppConnectionBinder();
77 private OnMessagePacketReceived messageListener = new OnMessagePacketReceived() {
78
79 @Override
80 public void onMessagePacketReceived(Account account,
81 MessagePacket packet) {
82 Message message = null;
83 boolean notify = false;
84 if ((packet.getType() == MessagePacket.TYPE_CHAT)) {
85 if (packet.hasChild("body")
86 && (packet.getBody().startsWith("?OTR"))) {
87 message = MessageParser.parseOtrChat(packet, account,
88 service);
89 notify = true;
90 } else if (packet.hasChild("body")) {
91 message = MessageParser.parsePlainTextChat(packet, account,
92 service);
93 notify = true;
94 } else if (packet.hasChild("received")
95 || (packet.hasChild("sent"))) {
96 message = MessageParser.parseCarbonMessage(packet, account,
97 service);
98 }
99
100 } else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
101 message = MessageParser
102 .parseGroupchat(packet, account, service);
103 if (message != null) {
104 notify = (message.getStatus() == Message.STATUS_RECIEVED);
105 }
106 } else if (packet.getType() == MessagePacket.TYPE_ERROR) {
107 message = MessageParser.parseError(packet,account,service);
108 } else {
109 Log.d(LOGTAG, "unparsed message " + packet.toString());
110 }
111 if (message == null) {
112 return;
113 }
114 if (packet.hasChild("delay")) {
115 try {
116 String stamp = packet.findChild("delay").getAttribute(
117 "stamp");
118 stamp = stamp.replace("Z", "+0000");
119 Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
120 .parse(stamp);
121 message.setTime(date.getTime());
122 } catch (ParseException e) {
123 Log.d(LOGTAG, "error trying to parse date" + e.getMessage());
124 }
125 }
126 if (notify) {
127 message.markUnread();
128 }
129 Conversation conversation = message.getConversation();
130 conversation.getMessages().add(message);
131 if (packet.getType() != MessagePacket.TYPE_ERROR) {
132 databaseBackend.createMessage(message);
133 }
134 if (convChangedListener != null) {
135 convChangedListener.onConversationListChanged();
136 } else {
137 if (notify) {
138 NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
139 mNotificationManager.notify(2342, UIHelper
140 .getUnreadMessageNotification(
141 getApplicationContext(), conversation));
142 }
143 }
144 }
145 };
146 private OnStatusChanged statusListener = new OnStatusChanged() {
147
148 @Override
149 public void onStatusChanged(Account account) {
150 if (accountChangedListener != null) {
151 accountChangedListener.onAccountListChangedListener();
152 }
153 if (account.getStatus() == Account.STATUS_ONLINE) {
154 databaseBackend.clearPresences(account);
155 connectMultiModeConversations(account);
156 List<Conversation> conversations = getConversations();
157 for (int i = 0; i < conversations.size(); ++i) {
158 if (conversations.get(i).getAccount() == account) {
159 sendUnsendMessages(conversations.get(i));
160 }
161 }
162 if (convChangedListener != null) {
163 convChangedListener.onConversationListChanged();
164 }
165 }
166 }
167 };
168
169 private OnPresencePacketReceived presenceListener = new OnPresencePacketReceived() {
170
171 @Override
172 public void onPresencePacketReceived(Account account,
173 PresencePacket packet) {
174 String[] fromParts = packet.getAttribute("from").split("/");
175 Contact contact = findContact(account, fromParts[0]);
176 if (contact == null) {
177 // most likely muc, self or roster not synced
178 Log.d(LOGTAG,"got presence for non contact "+packet.toString());
179 return;
180 }
181 String type = packet.getAttribute("type");
182 if (type == null) {
183 Element show = packet.findChild("show");
184 if (show == null) {
185 contact.updatePresence(fromParts[1], Presences.ONLINE);
186 } else if (show.getContent().equals("away")) {
187 contact.updatePresence(fromParts[1], Presences.AWAY);
188 } else if (show.getContent().equals("xa")) {
189 contact.updatePresence(fromParts[1], Presences.XA);
190 } else if (show.getContent().equals("chat")) {
191 contact.updatePresence(fromParts[1], Presences.CHAT);
192 } else if (show.getContent().equals("dnd")) {
193 contact.updatePresence(fromParts[1], Presences.DND);
194 }
195 databaseBackend.updateContact(contact);
196 } else if (type.equals("unavailable")) {
197 if (fromParts.length != 2) {
198 // Log.d(LOGTAG,"received presence with no resource "+packet.toString());
199 } else {
200 contact.removePresence(fromParts[1]);
201 databaseBackend.updateContact(contact);
202 }
203 } else if (type.equals("subscribe")) {
204 Log.d(LOGTAG,account.getJid()+": "+contact.getJid()+" asked to subscribe");
205 if (contact.getSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT)) {
206 Log.d(LOGTAG,"preemptive grant existed. granting");
207 sendPresenceUpdatesTo(contact);
208 contact.setSubscriptionOption(Contact.Subscription.FROM);
209 contact.resetSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
210 replaceContactInConversation(contact.getJid(), contact);
211 databaseBackend.updateContact(contact);
212 } else {
213 //TODO: ask user to handle it maybe
214 }
215 } else {
216 Log.d(LOGTAG,packet.toString());
217 }
218 replaceContactInConversation(contact.getJid(),contact);
219 }
220 };
221
222 private OnIqPacketReceived unknownIqListener = new OnIqPacketReceived() {
223
224 @Override
225 public void onIqPacketReceived(Account account, IqPacket packet) {
226 if (packet.hasChild("query")) {
227 Element query = packet.findChild("query");
228 String xmlns = query.getAttribute("xmlns");
229 if ((xmlns != null) && (xmlns.equals("jabber:iq:roster"))) {
230 processRosterItems(account, query);
231 mergePhoneContactsWithRoster(null);
232 }
233 }
234 }
235 };
236
237 private void processRosterItems(Account account, Element elements) {
238 for (Element item : elements.getChildren()) {
239 if (item.getName().equals("item")) {
240 String jid = item.getAttribute("jid");
241 String subscription = item.getAttribute("subscription");
242 Contact contact = databaseBackend.findContact(account, jid);
243 if (contact == null) {
244 if (!subscription.equals("remove")) {
245 String name = item.getAttribute("name");
246 if (name == null) {
247 name = jid.split("@")[0];
248 }
249 contact = new Contact(account, name, jid, null);
250 contact.parseSubscriptionFromElement(item);
251 databaseBackend.createContact(contact);
252 }
253 } else {
254 if (subscription.equals("remove")) {
255 databaseBackend.deleteContact(contact);
256 replaceContactInConversation(contact.getJid(), null);
257 } else {
258 contact.parseSubscriptionFromElement(item);
259 databaseBackend.updateContact(contact);
260 replaceContactInConversation(contact.getJid(),contact);
261 }
262 }
263 }
264 }
265 }
266
267 private void replaceContactInConversation(String jid, Contact contact) {
268 List<Conversation> conversations = getConversations();
269 for (int i = 0; i < conversations.size(); ++i) {
270 if ((conversations.get(i).getContactJid().equals(jid))) {
271 conversations.get(i).setContact(contact);
272 break;
273 }
274 }
275 }
276
277 public class XmppConnectionBinder extends Binder {
278 public XmppConnectionService getService() {
279 return XmppConnectionService.this;
280 }
281 }
282
283 @Override
284 public int onStartCommand(Intent intent, int flags, int startId) {
285 for (Account account : accounts) {
286 if (account.getXmppConnection() == null) {
287 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
288 account.setXmppConnection(this.createConnection(account));
289 }
290 }
291 }
292 return START_STICKY;
293 }
294
295 @Override
296 public void onCreate() {
297 databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
298 this.accounts = databaseBackend.getAccounts();
299
300 getContentResolver().registerContentObserver(
301 ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
302 }
303
304 @Override
305 public void onDestroy() {
306 super.onDestroy();
307 for (Account account : accounts) {
308 if (account.getXmppConnection() != null) {
309 disconnect(account);
310 }
311 }
312 }
313
314 public XmppConnection createConnection(Account account) {
315 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
316 XmppConnection connection = new XmppConnection(account, pm);
317 connection.setOnMessagePacketReceivedListener(this.messageListener);
318 connection.setOnStatusChangedListener(this.statusListener);
319 connection.setOnPresencePacketReceivedListener(this.presenceListener);
320 connection
321 .setOnUnregisteredIqPacketReceivedListener(this.unknownIqListener);
322 Thread thread = new Thread(connection);
323 thread.start();
324 return connection;
325 }
326
327 public void sendMessage(Account account, Message message, String presence) {
328 Conversation conv = message.getConversation();
329 boolean saveInDb = false;
330 boolean addToConversation = false;
331 if (account.getStatus() == Account.STATUS_ONLINE) {
332 MessagePacket packet;
333 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
334 if (!conv.hasValidOtrSession()) {
335 // starting otr session. messages will be send later
336 conv.startOtrSession(getApplicationContext(), presence);
337 } else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
338 // otr session aleary exists, creating message packet
339 // accordingly
340 packet = prepareMessagePacket(account, message,
341 conv.getOtrSession());
342 account.getXmppConnection().sendMessagePacket(packet);
343 message.setStatus(Message.STATUS_SEND);
344 }
345 saveInDb = true;
346 addToConversation = true;
347 } else {
348 // don't encrypt
349 if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
350 message.setStatus(Message.STATUS_SEND);
351 saveInDb = true;
352 addToConversation = true;
353 }
354
355 packet = prepareMessagePacket(account, message, null);
356 account.getXmppConnection().sendMessagePacket(packet);
357 }
358 } else {
359 // account is offline
360 saveInDb = true;
361 addToConversation = true;
362
363 }
364 if (saveInDb) {
365 databaseBackend.createMessage(message);
366 }
367 if (addToConversation) {
368 conv.getMessages().add(message);
369 if (convChangedListener != null) {
370 convChangedListener.onConversationListChanged();
371 }
372 }
373
374 }
375
376 private void sendUnsendMessages(Conversation conversation) {
377 for (int i = 0; i < conversation.getMessages().size(); ++i) {
378 if (conversation.getMessages().get(i).getStatus() == Message.STATUS_UNSEND) {
379 Message message = conversation.getMessages().get(i);
380 MessagePacket packet = prepareMessagePacket(
381 conversation.getAccount(), message, null);
382 conversation.getAccount().getXmppConnection()
383 .sendMessagePacket(packet);
384 message.setStatus(Message.STATUS_SEND);
385 if (conversation.getMode() == Conversation.MODE_SINGLE) {
386 databaseBackend.updateMessage(message);
387 } else {
388 databaseBackend.deleteMessage(message);
389 conversation.getMessages().remove(i);
390 i--;
391 }
392 }
393 }
394 }
395
396 public MessagePacket prepareMessagePacket(Account account, Message message,
397 Session otrSession) {
398 MessagePacket packet = new MessagePacket();
399 if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
400 packet.setType(MessagePacket.TYPE_CHAT);
401 if (otrSession != null) {
402 try {
403 packet.setBody(otrSession.transformSending(message
404 .getBody()));
405 } catch (OtrException e) {
406 Log.d(LOGTAG,
407 account.getJid()
408 + ": could not encrypt message to "
409 + message.getCounterpart());
410 }
411 Element privateMarker = new Element("private");
412 privateMarker.setAttribute("xmlns", "urn:xmpp:carbons:2");
413 packet.addChild(privateMarker);
414 packet.setTo(otrSession.getSessionID().getAccountID() + "/"
415 + otrSession.getSessionID().getUserID());
416 packet.setFrom(account.getFullJid());
417 } else {
418 packet.setBody(message.getBody());
419 packet.setTo(message.getCounterpart());
420 packet.setFrom(account.getJid());
421 }
422 } else if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
423 packet.setType(MessagePacket.TYPE_GROUPCHAT);
424 packet.setBody(message.getBody());
425 packet.setTo(message.getCounterpart());
426 packet.setFrom(account.getJid());
427 }
428 return packet;
429 }
430
431 public void getRoster(Account account,
432 final OnRosterFetchedListener listener) {
433 List<Contact> contacts = databaseBackend.getContacts(account);
434 for (int i = 0; i < contacts.size(); ++i) {
435 contacts.get(i).setAccount(account);
436 }
437 if (listener != null) {
438 listener.onRosterFetched(contacts);
439 }
440 }
441
442 public void updateRoster(final Account account,
443 final OnRosterFetchedListener listener) {
444 IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
445 Element query = new Element("query");
446 query.setAttribute("xmlns", "jabber:iq:roster");
447 query.setAttribute("ver", account.getRosterVersion());
448 iqPacket.addChild(query);
449 account.getXmppConnection().sendIqPacket(iqPacket,
450 new OnIqPacketReceived() {
451
452 @Override
453 public void onIqPacketReceived(final Account account,
454 IqPacket packet) {
455 Element roster = packet.findChild("query");
456 if (roster != null) {
457 String version = roster.getAttribute("ver");
458 processRosterItems(account, roster);
459 if (version!=null) {
460 account.setRosterVersion(version);
461 databaseBackend.updateAccount(account);
462 } else {
463 StringBuilder mWhere = new StringBuilder();
464 mWhere.append("jid NOT IN(");
465 List<Element> items = roster.getChildren();
466 for(int i = 0; i < items.size(); ++i) {
467 mWhere.append("\"");
468 mWhere.append(DatabaseUtils.sqlEscapeString(items.get(i).getAttribute("jid")));
469 if (i != items.size() - 1) {
470 mWhere.append("\",");
471 } else {
472 mWhere.append("\"");
473 }
474 }
475 mWhere.append(") and accountUuid = \"");
476 mWhere.append(account.getUuid());
477 mWhere.append("\"");
478 List<Contact> contactsToDelete = databaseBackend.getContats(mWhere.toString());
479 for(Contact contact : contactsToDelete) {
480 databaseBackend.deleteContact(contact);
481 replaceContactInConversation(contact.getJid(), null);
482 }
483 }
484 mergePhoneContactsWithRoster(new OnPhoneContactsMerged() {
485
486 @Override
487 public void phoneContactsMerged() {
488 if (listener != null) {
489 getRoster(account, listener);
490 }
491 }
492 });
493 } else {
494 if (listener != null) {
495 getRoster(account, listener);
496 }
497 }
498 }
499 });
500 }
501
502 public void mergePhoneContactsWithRoster(final OnPhoneContactsMerged listener) {
503 PhoneHelper.loadPhoneContacts(getApplicationContext(),
504 new OnPhoneContactsLoadedListener() {
505 @Override
506 public void onPhoneContactsLoaded(
507 Hashtable<String, Bundle> phoneContacts) {
508 List<Contact> contacts = databaseBackend
509 .getContacts(null);
510 for (int i = 0; i < contacts.size(); ++i) {
511 Contact contact = contacts.get(i);
512 if (phoneContacts.containsKey(contact.getJid())) {
513 Bundle phoneContact = phoneContacts.get(contact
514 .getJid());
515 String systemAccount = phoneContact
516 .getInt("phoneid")
517 + "#"
518 + phoneContact.getString("lookup");
519 contact.setSystemAccount(systemAccount);
520 contact.setPhotoUri(phoneContact
521 .getString("photouri"));
522 contact.setDisplayName(phoneContact
523 .getString("displayname"));
524 databaseBackend.updateContact(contact);
525 replaceContactInConversation(contact.getJid(), contact);
526 } else {
527 if ((contact.getSystemAccount() != null)
528 || (contact.getProfilePhoto() != null)) {
529 contact.setSystemAccount(null);
530 contact.setPhotoUri(null);
531 databaseBackend.updateContact(contact);
532 replaceContactInConversation(contact.getJid(), contact);
533 }
534 }
535 }
536 if (listener!=null) {
537 listener.phoneContactsMerged();
538 }
539 }
540 });
541 }
542
543 public List<Conversation> getConversations() {
544 if (this.conversations == null) {
545 Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
546 for (Account account : this.accounts) {
547 accountLookupTable.put(account.getUuid(), account);
548 }
549 this.conversations = databaseBackend
550 .getConversations(Conversation.STATUS_AVAILABLE);
551 for (Conversation conv : this.conversations) {
552 Account account = accountLookupTable.get(conv.getAccountUuid());
553 conv.setAccount(account);
554 conv.setContact(findContact(account, conv.getContactJid()));
555 conv.setMessages(databaseBackend.getMessages(conv, 50));
556 }
557 }
558 return this.conversations;
559 }
560
561 public List<Account> getAccounts() {
562 return this.accounts;
563 }
564
565 public Contact findContact(Account account, String jid) {
566 Contact contact = databaseBackend.findContact(account, jid);
567 if (contact!=null) {
568 contact.setAccount(account);
569 }
570 return contact;
571 }
572
573 public Conversation findOrCreateConversation(Account account, String jid,
574 boolean muc) {
575 for (Conversation conv : this.getConversations()) {
576 if ((conv.getAccount().equals(account))
577 && (conv.getContactJid().equals(jid))) {
578 return conv;
579 }
580 }
581 Conversation conversation = databaseBackend.findConversation(account,
582 jid);
583 if (conversation != null) {
584 conversation.setStatus(Conversation.STATUS_AVAILABLE);
585 conversation.setAccount(account);
586 if (muc) {
587 conversation.setMode(Conversation.MODE_MULTI);
588 if (account.getStatus() == Account.STATUS_ONLINE) {
589 joinMuc(conversation);
590 }
591 } else {
592 conversation.setMode(Conversation.MODE_SINGLE);
593 }
594 this.databaseBackend.updateConversation(conversation);
595 conversation.setContact(findContact(account,
596 conversation.getContactJid()));
597 } else {
598 String conversationName;
599 Contact contact = findContact(account, jid);
600 if (contact != null) {
601 conversationName = contact.getDisplayName();
602 } else {
603 conversationName = jid.split("@")[0];
604 }
605 if (muc) {
606 conversation = new Conversation(conversationName, account, jid,
607 Conversation.MODE_MULTI);
608 if (account.getStatus() == Account.STATUS_ONLINE) {
609 joinMuc(conversation);
610 }
611 } else {
612 conversation = new Conversation(conversationName, account, jid,
613 Conversation.MODE_SINGLE);
614 }
615 conversation.setContact(contact);
616 this.databaseBackend.createConversation(conversation);
617 }
618 this.conversations.add(conversation);
619 if (this.convChangedListener != null) {
620 this.convChangedListener.onConversationListChanged();
621 }
622 return conversation;
623 }
624
625 public void archiveConversation(Conversation conversation) {
626 if (conversation.getMode() == Conversation.MODE_MULTI) {
627 leaveMuc(conversation);
628 } else {
629 try {
630 conversation.endOtrIfNeeded();
631 } catch (OtrException e) {
632 Log.d(LOGTAG,
633 "error ending otr session for "
634 + conversation.getName());
635 }
636 }
637 this.databaseBackend.updateConversation(conversation);
638 this.conversations.remove(conversation);
639 if (this.convChangedListener != null) {
640 this.convChangedListener.onConversationListChanged();
641 }
642 }
643
644 public int getConversationCount() {
645 return this.databaseBackend.getConversationCount();
646 }
647
648 public void createAccount(Account account) {
649 databaseBackend.createAccount(account);
650 this.accounts.add(account);
651 account.setXmppConnection(this.createConnection(account));
652 if (accountChangedListener != null)
653 accountChangedListener.onAccountListChangedListener();
654 }
655
656 public void deleteContact(Contact contact) {
657 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
658 Element query = new Element("query");
659 query.setAttribute("xmlns", "jabber:iq:roster");
660 Element item = new Element("item");
661 item.setAttribute("jid", contact.getJid());
662 item.setAttribute("subscription", "remove");
663 query.addChild(item);
664 iq.addChild(query);
665 contact.getAccount().getXmppConnection().sendIqPacket(iq, null);
666 replaceContactInConversation(contact.getJid(), null);
667 databaseBackend.deleteContact(contact);
668 }
669
670 public void updateAccount(Account account) {
671 databaseBackend.updateAccount(account);
672 if (account.getXmppConnection() != null) {
673 disconnect(account);
674 }
675 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
676 account.setXmppConnection(this.createConnection(account));
677 }
678 if (accountChangedListener != null)
679 accountChangedListener.onAccountListChangedListener();
680 }
681
682 public void deleteAccount(Account account) {
683 Log.d(LOGTAG, "called delete account");
684 if (account.getXmppConnection() != null) {
685 this.disconnect(account);
686 }
687 databaseBackend.deleteAccount(account);
688 this.accounts.remove(account);
689 if (accountChangedListener != null)
690 accountChangedListener.onAccountListChangedListener();
691 }
692
693 public void setOnConversationListChangedListener(
694 OnConversationListChangedListener listener) {
695 this.convChangedListener = listener;
696 }
697
698 public void removeOnConversationListChangedListener() {
699 this.convChangedListener = null;
700 }
701
702 public void setOnAccountListChangedListener(
703 OnAccountListChangedListener listener) {
704 this.accountChangedListener = listener;
705 }
706
707 public void removeOnAccountListChangedListener() {
708 this.accountChangedListener = null;
709 }
710
711 public void connectMultiModeConversations(Account account) {
712 List<Conversation> conversations = getConversations();
713 for (int i = 0; i < conversations.size(); i++) {
714 Conversation conversation = conversations.get(i);
715 if ((conversation.getMode() == Conversation.MODE_MULTI)
716 && (conversation.getAccount() == account)) {
717 joinMuc(conversation);
718 }
719 }
720 }
721
722 public void joinMuc(Conversation conversation) {
723 String muc = conversation.getContactJid();
724 PresencePacket packet = new PresencePacket();
725 packet.setAttribute("to", muc + "/"
726 + conversation.getAccount().getUsername());
727 Element x = new Element("x");
728 x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
729 if (conversation.getMessages().size() != 0) {
730 Element history = new Element("history");
731 long lastMsgTime = conversation.getLatestMessage().getTimeSent();
732 long diff = (System.currentTimeMillis() - lastMsgTime) / 1000 - 1;
733 history.setAttribute("seconds", diff + "");
734 x.addChild(history);
735 }
736 packet.addChild(x);
737 conversation.getAccount().getXmppConnection()
738 .sendPresencePacket(packet);
739 }
740
741 public void leaveMuc(Conversation conversation) {
742
743 }
744
745 public void disconnect(Account account) {
746 List<Conversation> conversations = getConversations();
747 for (int i = 0; i < conversations.size(); i++) {
748 Conversation conversation = conversations.get(i);
749 if (conversation.getAccount() == account) {
750 if (conversation.getMode() == Conversation.MODE_MULTI) {
751 leaveMuc(conversation);
752 } else {
753 try {
754 conversation.endOtrIfNeeded();
755 } catch (OtrException e) {
756 Log.d(LOGTAG, "error ending otr session for "
757 + conversation.getName());
758 }
759 }
760 }
761 }
762 account.getXmppConnection().disconnect();
763 Log.d(LOGTAG, "disconnected account: " + account.getJid());
764 account.setXmppConnection(null);
765 }
766
767 @Override
768 public IBinder onBind(Intent intent) {
769 return mBinder;
770 }
771
772 public void updateContact(Contact contact) {
773 databaseBackend.updateContact(contact);
774 }
775
776 public void createContact(Contact contact) {
777 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
778 Element query = new Element("query");
779 query.setAttribute("xmlns", "jabber:iq:roster");
780 Element item = new Element("item");
781 item.setAttribute("jid", contact.getJid());
782 item.setAttribute("name", contact.getJid());
783 query.addChild(item);
784 iq.addChild(query);
785 Account account = contact.getAccount();
786 Log.d(LOGTAG,account.getJid()+": adding "+contact.getJid()+" to roster");
787 account.getXmppConnection().sendIqPacket(iq, null);
788 replaceContactInConversation(contact.getJid(), contact);
789 databaseBackend.createContact(contact);
790 }
791
792 public void requestPresenceUpdatesFrom(Contact contact) {
793 //Requesting a Subscription type=subscribe
794 PresencePacket packet = new PresencePacket();
795 packet.setAttribute("type", "subscribe");
796 packet.setAttribute("to", contact.getJid());
797 packet.setAttribute("from",contact.getAccount().getJid());
798 Log.d(LOGTAG,packet.toString());
799 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
800 }
801
802 public void stopPresenceUpdatesFrom(Contact contact) {
803 //Unsubscribing type='unsubscribe'
804 PresencePacket packet = new PresencePacket();
805 packet.setAttribute("type", "unsubscribe");
806 packet.setAttribute("to", contact.getJid());
807 packet.setAttribute("from",contact.getAccount().getJid());
808 Log.d(LOGTAG,packet.toString());
809 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
810 }
811
812 public void stopPresenceUpdatesTo(Contact contact) {
813 //Canceling a Subscription type=unsubscribed
814 PresencePacket packet = new PresencePacket();
815 packet.setAttribute("type", "unsubscribed");
816 packet.setAttribute("to", contact.getJid());
817 packet.setAttribute("from",contact.getAccount().getJid());
818 Log.d(LOGTAG,packet.toString());
819 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
820 }
821
822 public void sendPresenceUpdatesTo(Contact contact) {
823 //type='subscribed'
824 PresencePacket packet = new PresencePacket();
825 packet.setAttribute("type", "subscribed");
826 packet.setAttribute("to", contact.getJid());
827 packet.setAttribute("from",contact.getAccount().getJid());
828 Log.d(LOGTAG,packet.toString());
829 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
830 }
831}