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