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