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