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 Log.d(LOGTAG,"set no internet status to account");
392 break;
393 } else {
394 if (account.getStatus() == Account.STATUS_NO_INTERNET) {
395 account.setStatus(Account.STATUS_OFFLINE);
396 }
397 }
398 if (account.getXmppConnection() == null) {
399 if ((!account.isOptionSet(Account.OPTION_DISABLED))&&(isConnected)) {
400 account.setXmppConnection(this.createConnection(account));
401 Thread thread = new Thread(account.getXmppConnection());
402 thread.start();
403 }
404 } else {
405 if ((!account.isOptionSet(Account.OPTION_DISABLED))&&(isConnected)) {
406 if (account.getStatus()==Account.STATUS_OFFLINE) {
407 Thread thread = new Thread(account.getXmppConnection());
408 thread.start();
409 }
410 } else {
411 disconnect(account);
412 }
413 }
414 }
415 return START_STICKY;
416 }
417
418 @Override
419 public void onCreate() {
420 databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
421 this.accounts = databaseBackend.getAccounts();
422
423 getContentResolver().registerContentObserver(
424 ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
425 this.pgpServiceConnection = new OpenPgpServiceConnection(
426 getApplicationContext(), "org.sufficientlysecure.keychain");
427 this.pgpServiceConnection.bindToService();
428
429
430 }
431
432 @Override
433 public void onDestroy() {
434 super.onDestroy();
435 for (Account account : accounts) {
436 if (account.getXmppConnection() != null) {
437 disconnect(account);
438 }
439 }
440 }
441
442 protected void scheduleWakeupCall(int seconds) {
443 Context context = getApplicationContext();
444 AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
445 Intent intent = new Intent(context, EventReceiver.class);
446 PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
447 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
448 SystemClock.elapsedRealtime() +
449 seconds * 1000, alarmIntent);
450 Log.d(LOGTAG,"wake up call scheduled in "+seconds+" seconds");
451 }
452
453 public XmppConnection createConnection(Account account) {
454 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
455 XmppConnection connection = new XmppConnection(account, pm);
456 connection.setOnMessagePacketReceivedListener(this.messageListener);
457 connection.setOnStatusChangedListener(this.statusListener);
458 connection.setOnPresencePacketReceivedListener(this.presenceListener);
459 connection
460 .setOnUnregisteredIqPacketReceivedListener(this.unknownIqListener);
461 return connection;
462 }
463
464 public void sendMessage(Message message, String presence) {
465 Account account = message.getConversation().getAccount();
466 Conversation conv = message.getConversation();
467 boolean saveInDb = false;
468 boolean addToConversation = false;
469 if (account.getStatus() == Account.STATUS_ONLINE) {
470 MessagePacket packet;
471 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
472 if (!conv.hasValidOtrSession()) {
473 // starting otr session. messages will be send later
474 conv.startOtrSession(getApplicationContext(), presence);
475 } else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
476 // otr session aleary exists, creating message packet
477 // accordingly
478 packet = prepareMessagePacket(account, message,
479 conv.getOtrSession());
480 account.getXmppConnection().sendMessagePacket(packet);
481 message.setStatus(Message.STATUS_SEND);
482 }
483 saveInDb = true;
484 addToConversation = true;
485 } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
486 long keyId = message.getConversation().getContact()
487 .getPgpKeyId();
488 packet = new MessagePacket();
489 packet.setType(MessagePacket.TYPE_CHAT);
490 packet.setFrom(message.getConversation().getAccount()
491 .getFullJid());
492 packet.setTo(message.getCounterpart());
493 packet.setBody("This is an XEP-0027 encryted message");
494 Element x = new Element("x");
495 x.setAttribute("xmlns", "jabber:x:encrypted");
496 x.setContent(this.getPgpEngine().encrypt(keyId,
497 message.getBody()));
498 packet.addChild(x);
499 account.getXmppConnection().sendMessagePacket(packet);
500 message.setStatus(Message.STATUS_SEND);
501 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
502 saveInDb = true;
503 addToConversation = true;
504 } else {
505 // don't encrypt
506 if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
507 message.setStatus(Message.STATUS_SEND);
508 saveInDb = true;
509 addToConversation = true;
510 }
511
512 packet = prepareMessagePacket(account, message, null);
513 account.getXmppConnection().sendMessagePacket(packet);
514 }
515 } else {
516 // account is offline
517 saveInDb = true;
518 addToConversation = true;
519
520 }
521 if (saveInDb) {
522 databaseBackend.createMessage(message);
523 }
524 if (addToConversation) {
525 conv.getMessages().add(message);
526 if (convChangedListener != null) {
527 convChangedListener.onConversationListChanged();
528 }
529 }
530
531 }
532
533 private void sendUnsendMessages(Conversation conversation) {
534 for (int i = 0; i < conversation.getMessages().size(); ++i) {
535 if (conversation.getMessages().get(i).getStatus() == Message.STATUS_UNSEND) {
536 Message message = conversation.getMessages().get(i);
537 MessagePacket packet = prepareMessagePacket(
538 conversation.getAccount(), message, null);
539 conversation.getAccount().getXmppConnection()
540 .sendMessagePacket(packet);
541 message.setStatus(Message.STATUS_SEND);
542 if (conversation.getMode() == Conversation.MODE_SINGLE) {
543 databaseBackend.updateMessage(message);
544 } else {
545 databaseBackend.deleteMessage(message);
546 conversation.getMessages().remove(i);
547 i--;
548 }
549 }
550 }
551 }
552
553 public MessagePacket prepareMessagePacket(Account account, Message message,
554 Session otrSession) {
555 MessagePacket packet = new MessagePacket();
556 if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
557 packet.setType(MessagePacket.TYPE_CHAT);
558 if (otrSession != null) {
559 try {
560 packet.setBody(otrSession.transformSending(message
561 .getBody()));
562 } catch (OtrException e) {
563 Log.d(LOGTAG,
564 account.getJid()
565 + ": could not encrypt message to "
566 + message.getCounterpart());
567 }
568 Element privateMarker = new Element("private");
569 privateMarker.setAttribute("xmlns", "urn:xmpp:carbons:2");
570 packet.addChild(privateMarker);
571 packet.setTo(otrSession.getSessionID().getAccountID() + "/"
572 + otrSession.getSessionID().getUserID());
573 packet.setFrom(account.getFullJid());
574 } else {
575 packet.setBody(message.getBody());
576 packet.setTo(message.getCounterpart());
577 packet.setFrom(account.getJid());
578 }
579 } else if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
580 packet.setType(MessagePacket.TYPE_GROUPCHAT);
581 packet.setBody(message.getBody());
582 packet.setTo(message.getCounterpart().split("/")[0]);
583 packet.setFrom(account.getJid());
584 }
585 return packet;
586 }
587
588 public void getRoster(Account account,
589 final OnRosterFetchedListener listener) {
590 List<Contact> contacts = databaseBackend.getContactsByAccount(account);
591 for (int i = 0; i < contacts.size(); ++i) {
592 contacts.get(i).setAccount(account);
593 }
594 if (listener != null) {
595 listener.onRosterFetched(contacts);
596 }
597 }
598
599 public void updateRoster(final Account account,
600 final OnRosterFetchedListener listener) {
601 IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
602 Element query = new Element("query");
603 query.setAttribute("xmlns", "jabber:iq:roster");
604 query.setAttribute("ver", account.getRosterVersion());
605 iqPacket.addChild(query);
606 account.getXmppConnection().sendIqPacket(iqPacket,
607 new OnIqPacketReceived() {
608
609 @Override
610 public void onIqPacketReceived(final Account account,
611 IqPacket packet) {
612 Element roster = packet.findChild("query");
613 if (roster != null) {
614 processRosterItems(account, roster);
615 StringBuilder mWhere = new StringBuilder();
616 mWhere.append("jid NOT IN(");
617 List<Element> items = roster.getChildren();
618 for (int i = 0; i < items.size(); ++i) {
619 mWhere.append(DatabaseUtils
620 .sqlEscapeString(items.get(i)
621 .getAttribute("jid")));
622 if (i != items.size() - 1) {
623 mWhere.append(",");
624 }
625 }
626 mWhere.append(") and accountUuid = \"");
627 mWhere.append(account.getUuid());
628 mWhere.append("\"");
629 List<Contact> contactsToDelete = databaseBackend
630 .getContacts(mWhere.toString());
631 for (Contact contact : contactsToDelete) {
632 databaseBackend.deleteContact(contact);
633 replaceContactInConversation(contact.getJid(),
634 null);
635 }
636
637 }
638 mergePhoneContactsWithRoster(new OnPhoneContactsMerged() {
639
640 @Override
641 public void phoneContactsMerged() {
642 if (listener != null) {
643 getRoster(account, listener);
644 }
645 }
646 });
647 }
648 });
649 }
650
651 public void mergePhoneContactsWithRoster(
652 final OnPhoneContactsMerged listener) {
653 PhoneHelper.loadPhoneContacts(getApplicationContext(),
654 new OnPhoneContactsLoadedListener() {
655 @Override
656 public void onPhoneContactsLoaded(
657 Hashtable<String, Bundle> phoneContacts) {
658 List<Contact> contacts = databaseBackend
659 .getContactsByAccount(null);
660 for (int i = 0; i < contacts.size(); ++i) {
661 Contact contact = contacts.get(i);
662 if (phoneContacts.containsKey(contact.getJid())) {
663 Bundle phoneContact = phoneContacts.get(contact
664 .getJid());
665 String systemAccount = phoneContact
666 .getInt("phoneid")
667 + "#"
668 + phoneContact.getString("lookup");
669 contact.setSystemAccount(systemAccount);
670 contact.setPhotoUri(phoneContact
671 .getString("photouri"));
672 contact.setDisplayName(phoneContact
673 .getString("displayname"));
674 databaseBackend.updateContact(contact);
675 replaceContactInConversation(contact.getJid(),
676 contact);
677 } else {
678 if ((contact.getSystemAccount() != null)
679 || (contact.getProfilePhoto() != null)) {
680 contact.setSystemAccount(null);
681 contact.setPhotoUri(null);
682 databaseBackend.updateContact(contact);
683 replaceContactInConversation(
684 contact.getJid(), contact);
685 }
686 }
687 }
688 if (listener != null) {
689 listener.phoneContactsMerged();
690 }
691 }
692 });
693 }
694
695 public List<Conversation> getConversations() {
696 if (this.conversations == null) {
697 Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
698 for (Account account : this.accounts) {
699 accountLookupTable.put(account.getUuid(), account);
700 }
701 this.conversations = databaseBackend
702 .getConversations(Conversation.STATUS_AVAILABLE);
703 for (Conversation conv : this.conversations) {
704 Account account = accountLookupTable.get(conv.getAccountUuid());
705 conv.setAccount(account);
706 conv.setContact(findContact(account, conv.getContactJid()));
707 conv.setMessages(databaseBackend.getMessages(conv, 50));
708 }
709 }
710 return this.conversations;
711 }
712
713 public List<Account> getAccounts() {
714 return this.accounts;
715 }
716
717 public Contact findContact(Account account, String jid) {
718 Contact contact = databaseBackend.findContact(account, jid);
719 if (contact != null) {
720 contact.setAccount(account);
721 }
722 return contact;
723 }
724
725 public Conversation findOrCreateConversation(Account account, String jid,
726 boolean muc) {
727 for (Conversation conv : this.getConversations()) {
728 if ((conv.getAccount().equals(account))
729 && (conv.getContactJid().split("/")[0].equals(jid))) {
730 return conv;
731 }
732 }
733 Conversation conversation = databaseBackend.findConversation(account,
734 jid);
735 if (conversation != null) {
736 conversation.setStatus(Conversation.STATUS_AVAILABLE);
737 conversation.setAccount(account);
738 if (muc) {
739 conversation.setMode(Conversation.MODE_MULTI);
740 if (account.getStatus() == Account.STATUS_ONLINE) {
741 joinMuc(conversation);
742 }
743 } else {
744 conversation.setMode(Conversation.MODE_SINGLE);
745 }
746 this.databaseBackend.updateConversation(conversation);
747 conversation.setContact(findContact(account,
748 conversation.getContactJid()));
749 } else {
750 String conversationName;
751 Contact contact = findContact(account, jid);
752 if (contact != null) {
753 conversationName = contact.getDisplayName();
754 } else {
755 conversationName = jid.split("@")[0];
756 }
757 if (muc) {
758 conversation = new Conversation(conversationName, account, jid,
759 Conversation.MODE_MULTI);
760 if (account.getStatus() == Account.STATUS_ONLINE) {
761 joinMuc(conversation);
762 }
763 } else {
764 conversation = new Conversation(conversationName, account, jid,
765 Conversation.MODE_SINGLE);
766 }
767 conversation.setContact(contact);
768 this.databaseBackend.createConversation(conversation);
769 }
770 this.conversations.add(conversation);
771 if (this.convChangedListener != null) {
772 this.convChangedListener.onConversationListChanged();
773 }
774 return conversation;
775 }
776
777 public void archiveConversation(Conversation conversation) {
778 if (conversation.getMode() == Conversation.MODE_MULTI) {
779 leaveMuc(conversation);
780 } else {
781 try {
782 conversation.endOtrIfNeeded();
783 } catch (OtrException e) {
784 Log.d(LOGTAG,
785 "error ending otr session for "
786 + conversation.getName());
787 }
788 }
789 this.databaseBackend.updateConversation(conversation);
790 this.conversations.remove(conversation);
791 if (this.convChangedListener != null) {
792 this.convChangedListener.onConversationListChanged();
793 }
794 }
795
796 public int getConversationCount() {
797 return this.databaseBackend.getConversationCount();
798 }
799
800 public void createAccount(Account account) {
801 databaseBackend.createAccount(account);
802 this.accounts.add(account);
803 account.setXmppConnection(this.createConnection(account));
804 if (accountChangedListener != null)
805 accountChangedListener.onAccountListChangedListener();
806 }
807
808 public void deleteContact(Contact contact) {
809 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
810 Element query = new Element("query");
811 query.setAttribute("xmlns", "jabber:iq:roster");
812 Element item = new Element("item");
813 item.setAttribute("jid", contact.getJid());
814 item.setAttribute("subscription", "remove");
815 query.addChild(item);
816 iq.addChild(query);
817 contact.getAccount().getXmppConnection().sendIqPacket(iq, null);
818 replaceContactInConversation(contact.getJid(), null);
819 databaseBackend.deleteContact(contact);
820 }
821
822 public void updateAccount(Account account) {
823 databaseBackend.updateAccount(account);
824 if (account.getXmppConnection() != null) {
825 disconnect(account);
826 }
827 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
828 account.setXmppConnection(this.createConnection(account));
829 }
830 if (accountChangedListener != null)
831 accountChangedListener.onAccountListChangedListener();
832 }
833
834 public void deleteAccount(Account account) {
835 Log.d(LOGTAG, "called delete account");
836 if (account.getXmppConnection() != null) {
837 this.disconnect(account);
838 }
839 databaseBackend.deleteAccount(account);
840 this.accounts.remove(account);
841 if (accountChangedListener != null)
842 accountChangedListener.onAccountListChangedListener();
843 }
844
845 public void setOnConversationListChangedListener(
846 OnConversationListChangedListener listener) {
847 this.convChangedListener = listener;
848 }
849
850 public void removeOnConversationListChangedListener() {
851 this.convChangedListener = null;
852 }
853
854 public void setOnAccountListChangedListener(
855 OnAccountListChangedListener listener) {
856 this.accountChangedListener = listener;
857 }
858
859 public void removeOnAccountListChangedListener() {
860 this.accountChangedListener = null;
861 }
862
863 public void connectMultiModeConversations(Account account) {
864 List<Conversation> conversations = getConversations();
865 for (int i = 0; i < conversations.size(); i++) {
866 Conversation conversation = conversations.get(i);
867 if ((conversation.getMode() == Conversation.MODE_MULTI)
868 && (conversation.getAccount() == account)) {
869 joinMuc(conversation);
870 }
871 }
872 }
873
874 public void joinMuc(Conversation conversation) {
875 String[] mucParts = conversation.getContactJid().split("/");
876 String muc;
877 String nick;
878 if (mucParts.length == 2) {
879 muc = mucParts[0];
880 nick = mucParts[1];
881 } else {
882 muc = mucParts[0];
883 nick = conversation.getAccount().getUsername();
884 }
885 PresencePacket packet = new PresencePacket();
886 packet.setAttribute("to", muc + "/"
887 + nick);
888 Element x = new Element("x");
889 x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
890 if (conversation.getMessages().size() != 0) {
891 Element history = new Element("history");
892 long lastMsgTime = conversation.getLatestMessage().getTimeSent();
893 long diff = (System.currentTimeMillis() - lastMsgTime) / 1000 - 1;
894 history.setAttribute("seconds", diff + "");
895 x.addChild(history);
896 }
897 packet.addChild(x);
898 conversation.getAccount().getXmppConnection()
899 .sendPresencePacket(packet);
900 }
901
902 private OnRenameListener renameListener = null;
903 public void setOnRenameListener(OnRenameListener listener) {
904 this.renameListener = listener;
905 }
906
907 public void renameInMuc(final Conversation conversation, final String nick) {
908 final MucOptions options = conversation.getMucOptions();
909 if (options.online()) {
910 options.setOnRenameListener(new OnRenameListener() {
911
912 @Override
913 public void onRename(boolean success) {
914 if (renameListener!=null) {
915 renameListener.onRename(success);
916 }
917 if (success) {
918 databaseBackend.updateConversation(conversation);
919 }
920 }
921 });
922 PresencePacket packet = new PresencePacket();
923 packet.setAttribute("to", conversation.getContactJid().split("/")[0]+"/"+nick);
924 packet.setAttribute("from", conversation.getAccount().getFullJid());
925
926 packet = conversation.getAccount().getXmppConnection().sendPresencePacket(packet, new OnPresencePacketReceived() {
927
928 @Override
929 public void onPresencePacketReceived(Account account, PresencePacket packet) {
930 final boolean changed;
931 String type = packet.getAttribute("type");
932 changed = (!"error".equals(type));
933 if (!changed) {
934 options.getOnRenameListener().onRename(false);
935 } else {
936 if (type==null) {
937 options.getOnRenameListener().onRename(true);
938 options.setNick(packet.getAttribute("from").split("/")[1]);
939 } else {
940 options.processPacket(packet);
941 }
942 }
943 }
944 });
945 } else {
946 String jid = conversation.getContactJid().split("/")[0]+"/"+nick;
947 conversation.setContactJid(jid);
948 databaseBackend.updateConversation(conversation);
949 if (conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
950 joinMuc(conversation);
951 }
952 }
953 }
954
955 public void leaveMuc(Conversation conversation) {
956 PresencePacket packet = new PresencePacket();
957 packet.setAttribute("to", conversation.getContactJid());
958 packet.setAttribute("from", conversation.getAccount().getFullJid());
959 packet.setAttribute("type","unavailable");
960 conversation.getAccount().getXmppConnection().sendPresencePacket(packet);
961 conversation.getMucOptions().setOffline();
962 }
963
964 public void disconnect(Account account) {
965 List<Conversation> conversations = getConversations();
966 for (int i = 0; i < conversations.size(); i++) {
967 Conversation conversation = conversations.get(i);
968 if (conversation.getAccount() == account) {
969 if (conversation.getMode() == Conversation.MODE_MULTI) {
970 leaveMuc(conversation);
971 } else {
972 try {
973 conversation.endOtrIfNeeded();
974 } catch (OtrException e) {
975 Log.d(LOGTAG, "error ending otr session for "
976 + conversation.getName());
977 }
978 }
979 }
980 }
981 account.getXmppConnection().disconnect();
982 Log.d(LOGTAG, "disconnected account: " + account.getJid());
983 account.setXmppConnection(null);
984 }
985
986 @Override
987 public IBinder onBind(Intent intent) {
988 return mBinder;
989 }
990
991 public void updateContact(Contact contact) {
992 databaseBackend.updateContact(contact);
993 replaceContactInConversation(contact.getJid(), contact);
994 }
995
996 public void updateMessage(Message message) {
997 databaseBackend.updateMessage(message);
998 }
999
1000 public void createContact(Contact contact) {
1001 SharedPreferences sharedPref = PreferenceManager
1002 .getDefaultSharedPreferences(getApplicationContext());
1003 boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
1004 if (autoGrant) {
1005 contact.setSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
1006 contact.setSubscriptionOption(Contact.Subscription.ASKING);
1007 }
1008 databaseBackend.createContact(contact);
1009 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
1010 Element query = new Element("query");
1011 query.setAttribute("xmlns", "jabber:iq:roster");
1012 Element item = new Element("item");
1013 item.setAttribute("jid", contact.getJid());
1014 item.setAttribute("name", contact.getJid());
1015 query.addChild(item);
1016 iq.addChild(query);
1017 Account account = contact.getAccount();
1018 account.getXmppConnection().sendIqPacket(iq, null);
1019 if (autoGrant) {
1020 requestPresenceUpdatesFrom(contact);
1021 }
1022 replaceContactInConversation(contact.getJid(), contact);
1023 }
1024
1025 public void requestPresenceUpdatesFrom(Contact contact) {
1026 // Requesting a Subscription type=subscribe
1027 PresencePacket packet = new PresencePacket();
1028 packet.setAttribute("type", "subscribe");
1029 packet.setAttribute("to", contact.getJid());
1030 packet.setAttribute("from", contact.getAccount().getJid());
1031 Log.d(LOGTAG, packet.toString());
1032 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
1033 }
1034
1035 public void stopPresenceUpdatesFrom(Contact contact) {
1036 // Unsubscribing type='unsubscribe'
1037 PresencePacket packet = new PresencePacket();
1038 packet.setAttribute("type", "unsubscribe");
1039 packet.setAttribute("to", contact.getJid());
1040 packet.setAttribute("from", contact.getAccount().getJid());
1041 Log.d(LOGTAG, packet.toString());
1042 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
1043 }
1044
1045 public void stopPresenceUpdatesTo(Contact contact) {
1046 // Canceling a Subscription type=unsubscribed
1047 PresencePacket packet = new PresencePacket();
1048 packet.setAttribute("type", "unsubscribed");
1049 packet.setAttribute("to", contact.getJid());
1050 packet.setAttribute("from", contact.getAccount().getJid());
1051 Log.d(LOGTAG, packet.toString());
1052 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
1053 }
1054
1055 public void sendPresenceUpdatesTo(Contact contact) {
1056 // type='subscribed'
1057 PresencePacket packet = new PresencePacket();
1058 packet.setAttribute("type", "subscribed");
1059 packet.setAttribute("to", contact.getJid());
1060 packet.setAttribute("from", contact.getAccount().getJid());
1061 Log.d(LOGTAG, packet.toString());
1062 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
1063 }
1064
1065 public void sendPgpPresence(Account account, String signature) {
1066 PresencePacket packet = new PresencePacket();
1067 packet.setAttribute("from", account.getFullJid());
1068 Element status = new Element("status");
1069 status.setContent("online");
1070 packet.addChild(status);
1071 Element x = new Element("x");
1072 x.setAttribute("xmlns", "jabber:x:signed");
1073 x.setContent(signature);
1074 packet.addChild(x);
1075 account.getXmppConnection().sendPresencePacket(packet);
1076 }
1077
1078 public void generatePgpAnnouncement(Account account)
1079 throws PgpEngine.UserInputRequiredException {
1080 if (account.getStatus() == Account.STATUS_ONLINE) {
1081 String signature = getPgpEngine().generateSignature("online");
1082 account.setKey("pgp_signature", signature);
1083 databaseBackend.updateAccount(account);
1084 sendPgpPresence(account, signature);
1085 }
1086 }
1087
1088 public void updateConversation(Conversation conversation) {
1089 this.databaseBackend.updateConversation(conversation);
1090 }
1091
1092 public Contact findContact(String uuid) {
1093 Contact contact = this.databaseBackend.getContact(uuid);
1094 for(Account account : getAccounts()) {
1095 if (contact.getAccountUuid().equals(account.getUuid())) {
1096 contact.setAccount(account);
1097 }
1098 }
1099 return contact;
1100 }
1101}