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