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