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