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