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