1package eu.siacs.conversations.services;
2
3import java.text.ParseException;
4import java.text.SimpleDateFormat;
5import java.util.Collections;
6import java.util.Comparator;
7import java.util.Date;
8import java.util.Hashtable;
9import java.util.List;
10import java.util.Locale;
11import java.util.Random;
12
13import org.openintents.openpgp.util.OpenPgpApi;
14import org.openintents.openpgp.util.OpenPgpServiceConnection;
15
16import net.java.otr4j.OtrException;
17import net.java.otr4j.session.Session;
18import net.java.otr4j.session.SessionStatus;
19import eu.siacs.conversations.crypto.PgpEngine;
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.parser.MessageParser;
28import eu.siacs.conversations.persistance.DatabaseBackend;
29import eu.siacs.conversations.persistance.FileBackend;
30import eu.siacs.conversations.persistance.OnPhoneContactsMerged;
31import eu.siacs.conversations.ui.OnAccountListChangedListener;
32import eu.siacs.conversations.ui.OnConversationListChangedListener;
33import eu.siacs.conversations.ui.OnRosterFetchedListener;
34import eu.siacs.conversations.ui.UiCallback;
35import eu.siacs.conversations.utils.ExceptionHelper;
36import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
37import eu.siacs.conversations.utils.PhoneHelper;
38import eu.siacs.conversations.utils.UIHelper;
39import eu.siacs.conversations.xml.Element;
40import eu.siacs.conversations.xmpp.OnBindListener;
41import eu.siacs.conversations.xmpp.OnIqPacketReceived;
42import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
43import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
44import eu.siacs.conversations.xmpp.OnStatusChanged;
45import eu.siacs.conversations.xmpp.OnTLSExceptionReceived;
46import eu.siacs.conversations.xmpp.XmppConnection;
47import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
48import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
49import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
50import eu.siacs.conversations.xmpp.stanzas.IqPacket;
51import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
52import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
53import android.app.AlarmManager;
54import android.app.PendingIntent;
55import android.app.Service;
56import android.content.Context;
57import android.content.Intent;
58import android.content.SharedPreferences;
59import android.database.ContentObserver;
60import android.database.DatabaseUtils;
61import android.net.ConnectivityManager;
62import android.net.NetworkInfo;
63import android.net.Uri;
64import android.os.Binder;
65import android.os.Bundle;
66import android.os.IBinder;
67import android.os.PowerManager;
68import android.os.PowerManager.WakeLock;
69import android.os.SystemClock;
70import android.preference.PreferenceManager;
71import android.provider.ContactsContract;
72import android.util.Log;
73
74public class XmppConnectionService extends Service {
75
76 protected static final String LOGTAG = "xmppService";
77 public DatabaseBackend databaseBackend;
78 private FileBackend fileBackend;
79
80 public long startDate;
81
82 private static final int PING_MAX_INTERVAL = 300;
83 private static final int PING_MIN_INTERVAL = 10;
84 private static final int PING_TIMEOUT = 5;
85 private static final int CONNECT_TIMEOUT = 60;
86 private static final long CARBON_GRACE_PERIOD = 60000L;
87
88 private MessageParser mMessageParser = new MessageParser(this);
89
90 private List<Account> accounts;
91 private List<Conversation> conversations = null;
92 private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
93 this);
94
95 private OnConversationListChangedListener convChangedListener = null;
96 private int convChangedListenerCount = 0;
97 private OnAccountListChangedListener accountChangedListener = null;
98 private OnTLSExceptionReceived tlsException = null;
99
100 public void setOnTLSExceptionReceivedListener(
101 OnTLSExceptionReceived listener) {
102 tlsException = listener;
103 }
104
105 private Random mRandom = new Random(System.currentTimeMillis());
106
107 private long lastCarbonMessageReceived = -CARBON_GRACE_PERIOD;
108
109 private ContentObserver contactObserver = new ContentObserver(null) {
110 @Override
111 public void onChange(boolean selfChange) {
112 super.onChange(selfChange);
113 Log.d(LOGTAG, "contact list has changed");
114 mergePhoneContactsWithRoster(null);
115 }
116 };
117
118 private final IBinder mBinder = new XmppConnectionBinder();
119 private OnMessagePacketReceived messageListener = new OnMessagePacketReceived() {
120
121 @Override
122 public void onMessagePacketReceived(Account account,
123 MessagePacket packet) {
124 Message message = null;
125 boolean notify = true;
126 if (getPreferences().getBoolean(
127 "notification_grace_period_after_carbon_received", true)) {
128 notify = (SystemClock.elapsedRealtime() - lastCarbonMessageReceived) > CARBON_GRACE_PERIOD;
129 }
130
131 if ((packet.getType() == MessagePacket.TYPE_CHAT)) {
132 String pgpBody = mMessageParser.getPgpBody(packet);
133 if (pgpBody != null) {
134 message = mMessageParser.parsePgpChat(pgpBody, packet,
135 account);
136 message.markUnread();
137 } else if ((packet.getBody() != null)
138 && (packet.getBody().startsWith("?OTR"))) {
139 message = mMessageParser.parseOtrChat(packet, account);
140 if (message != null) {
141 message.markUnread();
142 }
143 } else if (packet.hasChild("body")) {
144 message = mMessageParser
145 .parsePlainTextChat(packet, account);
146 message.markUnread();
147 } else if (packet.hasChild("received")
148 || (packet.hasChild("sent"))) {
149 message = mMessageParser
150 .parseCarbonMessage(packet, account);
151 if (message != null) {
152 if (message.getStatus() == Message.STATUS_SEND) {
153 lastCarbonMessageReceived = SystemClock
154 .elapsedRealtime();
155 notify = false;
156 message.getConversation().markRead();
157 } else {
158 message.markUnread();
159 }
160 }
161 }
162
163 } else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
164 message = mMessageParser.parseGroupchat(packet, account);
165 if (message != null) {
166 if (message.getStatus() == Message.STATUS_RECIEVED) {
167 message.markUnread();
168 } else {
169 message.getConversation().markRead();
170 notify = false;
171 }
172 }
173 } else if (packet.getType() == MessagePacket.TYPE_ERROR) {
174 mMessageParser.parseError(packet, account);
175 return;
176 } else if (packet.getType() == MessagePacket.TYPE_NORMAL) {
177 if (packet.hasChild("x")) {
178 Element x = packet.findChild("x");
179 if (x.hasChild("invite")) {
180 findOrCreateConversation(account, packet.getFrom(),
181 true);
182 if (convChangedListener != null) {
183 convChangedListener.onConversationListChanged();
184 }
185 Log.d(LOGTAG,
186 "invitation received to " + packet.getFrom());
187 }
188
189 } else {
190 // Log.d(LOGTAG, "unparsed message " + packet.toString());
191 }
192 }
193 if ((message == null) || (message.getBody() == null)) {
194 return;
195 }
196 if (packet.hasChild("delay")) {
197 try {
198 String stamp = packet.findChild("delay").getAttribute(
199 "stamp");
200 stamp = stamp.replace("Z", "+0000");
201 Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
202 .parse(stamp);
203 message.setTime(date.getTime());
204 } catch (ParseException e) {
205 Log.d(LOGTAG, "error trying to parse date" + e.getMessage());
206 }
207 }
208 Conversation conversation = message.getConversation();
209 conversation.getMessages().add(message);
210 if (packet.getType() != MessagePacket.TYPE_ERROR) {
211 databaseBackend.createMessage(message);
212 }
213 if (convChangedListener != null) {
214 convChangedListener.onConversationListChanged();
215 } else {
216 UIHelper.updateNotification(getApplicationContext(),
217 getConversations(), message.getConversation(), notify);
218 }
219 }
220 };
221 private OnStatusChanged statusListener = new OnStatusChanged() {
222
223 @Override
224 public void onStatusChanged(Account account) {
225 if (accountChangedListener != null) {
226 accountChangedListener.onAccountListChangedListener();
227 }
228 if (account.getStatus() == Account.STATUS_ONLINE) {
229 List<Conversation> conversations = getConversations();
230 for (int i = 0; i < conversations.size(); ++i) {
231 if (conversations.get(i).getAccount() == account) {
232 sendUnsendMessages(conversations.get(i));
233 }
234 }
235 scheduleWakeupCall(PING_MAX_INTERVAL, true);
236 } else if (account.getStatus() == Account.STATUS_OFFLINE) {
237 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
238 int timeToReconnect = mRandom.nextInt(50) + 10;
239 scheduleWakeupCall(timeToReconnect, false);
240 }
241
242 } else if (account.getStatus() == Account.STATUS_REGISTRATION_SUCCESSFULL) {
243 databaseBackend.updateAccount(account);
244 reconnectAccount(account, true);
245 } else {
246 UIHelper.showErrorNotification(getApplicationContext(),
247 getAccounts());
248 }
249 }
250 };
251
252 private OnPresencePacketReceived presenceListener = new OnPresencePacketReceived() {
253
254 @Override
255 public void onPresencePacketReceived(final Account account,
256 PresencePacket packet) {
257 if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) {
258 Conversation muc = findMuc(
259 packet.getAttribute("from").split("/")[0], account);
260 if (muc != null) {
261 muc.getMucOptions().processPacket(packet);
262 } else {
263 Log.d(LOGTAG, account.getJid()
264 + ": could not find muc for received muc package "
265 + packet.toString());
266 }
267 } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
268 Conversation muc = findMuc(
269 packet.getAttribute("from").split("/")[0], account);
270 if (muc != null) {
271 Log.d(LOGTAG,
272 account.getJid() + ": reading muc status packet "
273 + packet.toString());
274 int error = muc.getMucOptions().getError();
275 muc.getMucOptions().processPacket(packet);
276 if ((muc.getMucOptions().getError() != error)
277 && (convChangedListener != null)) {
278 Log.d(LOGTAG, "muc error status changed");
279 convChangedListener.onConversationListChanged();
280 }
281 }
282 } else {
283 String[] fromParts = packet.getAttribute("from").split("/");
284 String type = packet.getAttribute("type");
285 if (fromParts[0].equals(account.getJid())) {
286 if (fromParts.length == 2) {
287 if (type == null) {
288 account.updatePresence(fromParts[1], Presences
289 .parseShow(packet.findChild("show")));
290 } else if (type.equals("unavailable")) {
291 account.removePresence(fromParts[1]);
292 }
293 }
294
295 } else {
296 Contact contact = findContact(account, fromParts[0]);
297 if (contact == null) {
298 if ("subscribe".equals(type)) {
299 account.getXmppConnection().addPendingSubscription(
300 fromParts[0]);
301 } else {
302 // Log.d(LOGTAG,packet.getFrom()+
303 // " could not be found");
304 }
305 return;
306 }
307 if (type == null) {
308 if (fromParts.length == 2) {
309 contact.updatePresence(fromParts[1], Presences
310 .parseShow(packet.findChild("show")));
311 PgpEngine pgp = getPgpEngine();
312 if (pgp != null) {
313 Element x = packet.findChild("x",
314 "jabber:x:signed");
315 if (x != null) {
316 Element status = packet.findChild("status");
317 String msg;
318 if (status != null) {
319 msg = status.getContent();
320 } else {
321 msg = "";
322 }
323 contact.setPgpKeyId(pgp.fetchKeyId(account,
324 msg, x.getContent()));
325 Log.d("xmppService",
326 account.getJid()
327 + ": fetched key id for "
328 + contact.getJid()
329 + " was:"
330 + contact.getPgpKeyId());
331 }
332 }
333 replaceContactInConversation(account,
334 contact.getJid(), contact);
335 databaseBackend.updateContact(contact, true);
336 } else {
337 // Log.d(LOGTAG,"presence without resource "+packet.toString());
338 }
339 } else if (type.equals("unavailable")) {
340 if (fromParts.length != 2) {
341 contact.clearPresences();
342 } else {
343 contact.removePresence(fromParts[1]);
344 }
345 replaceContactInConversation(account, contact.getJid(),
346 contact);
347 databaseBackend.updateContact(contact, true);
348 } else if (type.equals("subscribe")) {
349 Log.d(LOGTAG, "received subscribe packet from "
350 + packet.getFrom());
351 if (contact
352 .getSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT)) {
353 Log.d(LOGTAG, "preemptive grant; granting");
354 sendPresenceUpdatesTo(contact);
355 contact.setSubscriptionOption(Contact.Subscription.FROM);
356 contact.resetSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
357 replaceContactInConversation(account,
358 contact.getJid(), contact);
359 databaseBackend.updateContact(contact, false);
360 if ((contact
361 .getSubscriptionOption(Contact.Subscription.ASKING))
362 && (!contact
363 .getSubscriptionOption(Contact.Subscription.TO))) {
364 requestPresenceUpdatesFrom(contact);
365 }
366 } else {
367 account.getXmppConnection().addPendingSubscription(
368 fromParts[0]);
369 }
370 } else {
371 // Log.d(LOGTAG, packet.toString());
372 }
373 }
374 }
375 }
376 };
377
378 private OnIqPacketReceived unknownIqListener = new OnIqPacketReceived() {
379
380 @Override
381 public void onIqPacketReceived(Account account, IqPacket packet) {
382 if (packet.hasChild("query", "jabber:iq:roster")) {
383 String from = packet.getFrom();
384 if ((from == null) || (from.equals(account.getJid()))) {
385 Element query = packet.findChild("query");
386 processRosterItems(account, query);
387 mergePhoneContactsWithRoster(null);
388 } else {
389 Log.d(LOGTAG, "unauthorized roster push from: " + from);
390 }
391 } else if (packet
392 .hasChild("open", "http://jabber.org/protocol/ibb")
393 || packet
394 .hasChild("data", "http://jabber.org/protocol/ibb")) {
395 XmppConnectionService.this.mJingleConnectionManager
396 .deliverIbbPacket(account, packet);
397 } else if (packet.hasChild("query",
398 "http://jabber.org/protocol/disco#info")) {
399 IqPacket iqResponse = packet
400 .generateRespone(IqPacket.TYPE_RESULT);
401 Element query = iqResponse.addChild("query",
402 "http://jabber.org/protocol/disco#info");
403 query.addChild("feature").setAttribute("var",
404 "urn:xmpp:jingle:1");
405 query.addChild("feature").setAttribute("var",
406 "urn:xmpp:jingle:apps:file-transfer:3");
407 query.addChild("feature").setAttribute("var",
408 "urn:xmpp:jingle:transports:s5b:1");
409 query.addChild("feature").setAttribute("var",
410 "urn:xmpp:jingle:transports:ibb:1");
411 account.getXmppConnection().sendIqPacket(iqResponse, null);
412 } else {
413 if ((packet.getType() == IqPacket.TYPE_GET)
414 || (packet.getType() == IqPacket.TYPE_SET)) {
415 IqPacket response = packet
416 .generateRespone(IqPacket.TYPE_ERROR);
417 Element error = response.addChild("error");
418 error.setAttribute("type", "cancel");
419 error.addChild("feature-not-implemented",
420 "urn:ietf:params:xml:ns:xmpp-stanzas");
421 account.getXmppConnection().sendIqPacket(response, null);
422 }
423 }
424 }
425 };
426
427 private OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() {
428
429 @Override
430 public void onJinglePacketReceived(Account account, JinglePacket packet) {
431 mJingleConnectionManager.deliverPacket(account, packet);
432 }
433 };
434
435 private OpenPgpServiceConnection pgpServiceConnection;
436 private PgpEngine mPgpEngine = null;
437 private Intent pingIntent;
438 private PendingIntent pendingPingIntent = null;
439 private WakeLock wakeLock;
440 private PowerManager pm;
441
442 public PgpEngine getPgpEngine() {
443 if (pgpServiceConnection.isBound()) {
444 if (this.mPgpEngine == null) {
445 this.mPgpEngine = new PgpEngine(new OpenPgpApi(
446 getApplicationContext(),
447 pgpServiceConnection.getService()), this);
448 }
449 return mPgpEngine;
450 } else {
451 return null;
452 }
453
454 }
455
456 public FileBackend getFileBackend() {
457 return this.fileBackend;
458 }
459
460 public Message attachImageToConversation(final Conversation conversation,
461 final Uri uri, final UiCallback callback) {
462 final Message message;
463 if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
464 message = new Message(conversation, "",
465 Message.ENCRYPTION_DECRYPTED);
466 } else {
467 message = new Message(conversation, "", Message.ENCRYPTION_NONE);
468 }
469 message.setPresence(conversation.getNextPresence());
470 message.setType(Message.TYPE_IMAGE);
471 message.setStatus(Message.STATUS_OFFERED);
472 new Thread(new Runnable() {
473
474 @Override
475 public void run() {
476 try {
477 getFileBackend().copyImageToPrivateStorage(message, uri);
478 if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
479 getPgpEngine().encrypt(message, callback);
480 } else {
481 callback.success();
482 }
483 } catch (FileBackend.ImageCopyException e) {
484 callback.error(e.getResId());
485 }
486 }
487 }).start();
488 return message;
489 }
490
491 protected Conversation findMuc(String name, Account account) {
492 for (Conversation conversation : this.conversations) {
493 if (conversation.getContactJid().split("/")[0].equals(name)
494 && (conversation.getAccount() == account)) {
495 return conversation;
496 }
497 }
498 return null;
499 }
500
501 private void processRosterItems(Account account, Element elements) {
502 String version = elements.getAttribute("ver");
503 if (version != null) {
504 account.setRosterVersion(version);
505 databaseBackend.updateAccount(account);
506 }
507 for (Element item : elements.getChildren()) {
508 if (item.getName().equals("item")) {
509 String jid = item.getAttribute("jid");
510 String subscription = item.getAttribute("subscription");
511 Contact contact = databaseBackend.findContact(account, jid);
512 if (contact == null) {
513 if (!subscription.equals("remove")) {
514 String name = item.getAttribute("name");
515 if (name == null) {
516 name = jid.split("@")[0];
517 }
518 contact = new Contact(account, name, jid, null);
519 contact.parseSubscriptionFromElement(item);
520 databaseBackend.createContact(contact);
521 }
522 } else {
523 if (subscription.equals("remove")) {
524 databaseBackend.deleteContact(contact);
525 replaceContactInConversation(account, contact.getJid(),
526 null);
527 } else {
528 contact.parseSubscriptionFromElement(item);
529 databaseBackend.updateContact(contact, false);
530 replaceContactInConversation(account, contact.getJid(),
531 contact);
532 }
533 }
534 }
535 }
536 }
537
538 private void replaceContactInConversation(Account account, String jid,
539 Contact contact) {
540 List<Conversation> conversations = getConversations();
541 for (Conversation c : conversations) {
542 if (c.getContactJid().equals(jid) && (c.getAccount() == account)) {
543 c.setContact(contact);
544 break;
545 }
546 }
547 }
548
549 public class XmppConnectionBinder extends Binder {
550 public XmppConnectionService getService() {
551 return XmppConnectionService.this;
552 }
553 }
554
555 @Override
556 public int onStartCommand(Intent intent, int flags, int startId) {
557 this.wakeLock.acquire();
558 // Log.d(LOGTAG,"calling start service. caller was:"+intent.getAction());
559 ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
560 .getSystemService(Context.CONNECTIVITY_SERVICE);
561 NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
562 boolean isConnected = activeNetwork != null
563 && activeNetwork.isConnected();
564
565 for (Account account : accounts) {
566 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
567 if (!isConnected) {
568 account.setStatus(Account.STATUS_NO_INTERNET);
569 if (statusListener != null) {
570 statusListener.onStatusChanged(account);
571 }
572 } else {
573 if (account.getStatus() == Account.STATUS_NO_INTERNET) {
574 account.setStatus(Account.STATUS_OFFLINE);
575 if (statusListener != null) {
576 statusListener.onStatusChanged(account);
577 }
578 }
579
580 // TODO 3 remaining cases
581 if (account.getStatus() == Account.STATUS_ONLINE) {
582 long lastReceived = account.getXmppConnection().lastPaketReceived;
583 long lastSent = account.getXmppConnection().lastPingSent;
584 if (lastSent - lastReceived >= PING_TIMEOUT * 1000) {
585 Log.d(LOGTAG, account.getJid() + ": ping timeout");
586 this.reconnectAccount(account, true);
587 } else if (SystemClock.elapsedRealtime() - lastReceived >= PING_MIN_INTERVAL * 1000) {
588 account.getXmppConnection().sendPing();
589 account.getXmppConnection().lastPingSent = SystemClock
590 .elapsedRealtime();
591 this.scheduleWakeupCall(2, false);
592 }
593 } else if (account.getStatus() == Account.STATUS_OFFLINE) {
594 if (account.getXmppConnection() == null) {
595 account.setXmppConnection(this
596 .createConnection(account));
597 }
598 account.getXmppConnection().lastPingSent = SystemClock
599 .elapsedRealtime();
600 new Thread(account.getXmppConnection()).start();
601 } else if ((account.getStatus() == Account.STATUS_CONNECTING)
602 && ((SystemClock.elapsedRealtime() - account
603 .getXmppConnection().lastConnect) / 1000 >= CONNECT_TIMEOUT)) {
604 Log.d(LOGTAG, account.getJid()
605 + ": time out during connect reconnecting");
606 reconnectAccount(account, true);
607 } else {
608 Log.d(LOGTAG,
609 "seconds since last connect:"
610 + ((SystemClock.elapsedRealtime() - account
611 .getXmppConnection().lastConnect) / 1000));
612 Log.d(LOGTAG,
613 account.getJid() + ": status="
614 + account.getStatus());
615 // TODO notify user of ssl cert problem or auth problem
616 // or what ever
617 }
618 // in any case. reschedule wakup call
619 this.scheduleWakeupCall(PING_MAX_INTERVAL, true);
620 }
621 if (accountChangedListener != null) {
622 accountChangedListener.onAccountListChangedListener();
623 }
624 }
625 }
626 if (wakeLock.isHeld()) {
627 wakeLock.release();
628 }
629 return START_STICKY;
630 }
631
632 @Override
633 public void onCreate() {
634 ExceptionHelper.init(getApplicationContext());
635 this.databaseBackend = DatabaseBackend
636 .getInstance(getApplicationContext());
637 this.fileBackend = new FileBackend(getApplicationContext());
638 this.accounts = databaseBackend.getAccounts();
639
640 this.getConversations();
641
642 getContentResolver().registerContentObserver(
643 ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
644 this.pgpServiceConnection = new OpenPgpServiceConnection(
645 getApplicationContext(), "org.sufficientlysecure.keychain");
646 this.pgpServiceConnection.bindToService();
647
648 this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
649 this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
650 "XmppConnectionService");
651 }
652
653 @Override
654 public void onDestroy() {
655 Log.d(LOGTAG, "stopping service");
656 super.onDestroy();
657 for (Account account : accounts) {
658 if (account.getXmppConnection() != null) {
659 disconnect(account, true);
660 }
661 }
662 }
663
664 protected void scheduleWakeupCall(int seconds, boolean ping) {
665 long timeToWake = SystemClock.elapsedRealtime() + seconds * 1000;
666 Context context = getApplicationContext();
667 AlarmManager alarmManager = (AlarmManager) context
668 .getSystemService(Context.ALARM_SERVICE);
669
670 if (ping) {
671 if (this.pingIntent == null) {
672 this.pingIntent = new Intent(context, EventReceiver.class);
673 this.pingIntent.setAction("ping");
674 this.pingIntent.putExtra("time", timeToWake);
675 this.pendingPingIntent = PendingIntent.getBroadcast(context, 0,
676 this.pingIntent, 0);
677 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
678 timeToWake, pendingPingIntent);
679 // Log.d(LOGTAG,"schedule ping in "+seconds+" seconds");
680 } else {
681 long scheduledTime = this.pingIntent.getLongExtra("time", 0);
682 if (scheduledTime < SystemClock.elapsedRealtime()
683 || (scheduledTime > timeToWake)) {
684 this.pingIntent.putExtra("time", timeToWake);
685 alarmManager.cancel(this.pendingPingIntent);
686 this.pendingPingIntent = PendingIntent.getBroadcast(
687 context, 0, this.pingIntent, 0);
688 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
689 timeToWake, pendingPingIntent);
690 // Log.d(LOGTAG,"reschedule old ping to ping in "+seconds+" seconds");
691 }
692 }
693 } else {
694 Intent intent = new Intent(context, EventReceiver.class);
695 intent.setAction("ping_check");
696 PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 0,
697 intent, 0);
698 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake,
699 alarmIntent);
700 }
701
702 }
703
704 public XmppConnection createConnection(Account account) {
705 SharedPreferences sharedPref = getPreferences();
706 account.setResource(sharedPref.getString("resource", "mobile")
707 .toLowerCase(Locale.getDefault()));
708 XmppConnection connection = new XmppConnection(account, this.pm);
709 connection.setOnMessagePacketReceivedListener(this.messageListener);
710 connection.setOnStatusChangedListener(this.statusListener);
711 connection.setOnPresencePacketReceivedListener(this.presenceListener);
712 connection
713 .setOnUnregisteredIqPacketReceivedListener(this.unknownIqListener);
714 connection.setOnJinglePacketReceivedListener(this.jingleListener);
715 connection
716 .setOnTLSExceptionReceivedListener(new OnTLSExceptionReceived() {
717
718 @Override
719 public void onTLSExceptionReceived(String fingerprint,
720 Account account) {
721 Log.d(LOGTAG, "tls exception arrived in service");
722 if (tlsException != null) {
723 tlsException.onTLSExceptionReceived(fingerprint,
724 account);
725 }
726 }
727 });
728 connection.setOnBindListener(new OnBindListener() {
729
730 @Override
731 public void onBind(Account account) {
732 databaseBackend.clearPresences(account);
733 account.clearPresences(); // self presences
734 if (account.getXmppConnection().hasFeatureRosterManagment()) {
735 updateRoster(account, null);
736 }
737 connectMultiModeConversations(account);
738 if (convChangedListener != null) {
739 convChangedListener.onConversationListChanged();
740 }
741 }
742 });
743 return connection;
744 }
745
746 synchronized public void sendMessage(Message message, String presence) {
747 Account account = message.getConversation().getAccount();
748 Conversation conv = message.getConversation();
749 MessagePacket packet = null;
750 boolean saveInDb = false;
751 boolean addToConversation = false;
752 boolean send = false;
753 if (account.getStatus() == Account.STATUS_ONLINE) {
754 if (message.getType() == Message.TYPE_IMAGE) {
755 mJingleConnectionManager.createNewConnection(message);
756 } else {
757 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
758 if (!conv.hasValidOtrSession()) {
759 // starting otr session. messages will be send later
760 conv.startOtrSession(getApplicationContext(), presence,
761 true);
762 } else if (conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
763 // otr session aleary exists, creating message packet
764 // accordingly
765 packet = prepareMessagePacket(account, message,
766 conv.getOtrSession());
767 send = true;
768 message.setStatus(Message.STATUS_SEND);
769 }
770 saveInDb = true;
771 addToConversation = true;
772 } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
773 message.getConversation().endOtrIfNeeded();
774 packet = prepareMessagePacket(account, message, null);
775 packet.setBody("This is an XEP-0027 encryted message");
776 packet.addChild("x", "jabber:x:encrypted").setContent(
777 message.getEncryptedBody());
778 message.setStatus(Message.STATUS_SEND);
779 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
780 saveInDb = true;
781 addToConversation = true;
782 send = true;
783 } else {
784 message.getConversation().endOtrIfNeeded();
785 // don't encrypt
786 if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
787 message.setStatus(Message.STATUS_SEND);
788 }
789 packet = prepareMessagePacket(account, message, null);
790 send = true;
791 saveInDb = true;
792 addToConversation = true;
793 }
794 }
795 } else {
796 if (message.getEncryption() == Message.ENCRYPTION_PGP) {
797 String pgpBody = message.getEncryptedBody();
798 String decryptedBody = message.getBody();
799 message.setBody(pgpBody);
800 databaseBackend.createMessage(message);
801 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
802 message.setBody(decryptedBody);
803 addToConversation = true;
804 } else {
805 saveInDb = true;
806 addToConversation = true;
807 }
808
809 }
810 if (saveInDb) {
811 databaseBackend.createMessage(message);
812 }
813 if (addToConversation) {
814 conv.getMessages().add(message);
815 if (convChangedListener != null) {
816 convChangedListener.onConversationListChanged();
817 }
818 }
819 if ((send) && (packet != null)) {
820 account.getXmppConnection().sendMessagePacket(packet);
821 }
822
823 }
824
825 private void sendUnsendMessages(Conversation conversation) {
826 for (int i = 0; i < conversation.getMessages().size(); ++i) {
827 if (conversation.getMessages().get(i).getStatus() == Message.STATUS_UNSEND) {
828 resendMessage(conversation.getMessages().get(i));
829 }
830 }
831 }
832
833 private void resendMessage(Message message) {
834 Account account = message.getConversation().getAccount();
835 MessagePacket packet = null;
836 if (message.getEncryption() == Message.ENCRYPTION_NONE) {
837 packet = prepareMessagePacket(account, message,null);
838 } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
839 packet = prepareMessagePacket(account, message, null);
840 packet.setBody("This is an XEP-0027 encryted message");
841 if (message.getEncryptedBody()==null) {
842 markMessage(message, Message.STATUS_SEND_FAILED);
843 return;
844 }
845 packet.addChild("x", "jabber:x:encrypted").setContent(
846 message.getEncryptedBody());
847 } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
848 packet = prepareMessagePacket(account, message, null);
849 packet.setBody("This is an XEP-0027 encryted message");
850 packet.addChild("x", "jabber:x:encrypted").setContent(
851 message.getBody());
852 }
853 if (packet!=null) {
854 account.getXmppConnection().sendMessagePacket(packet);
855 markMessage(message, Message.STATUS_SEND);
856 }
857 }
858
859 public MessagePacket prepareMessagePacket(Account account, Message message,
860 Session otrSession) {
861 MessagePacket packet = new MessagePacket();
862 if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
863 packet.setType(MessagePacket.TYPE_CHAT);
864 if (otrSession != null) {
865 try {
866 packet.setBody(otrSession.transformSending(message
867 .getBody()));
868 } catch (OtrException e) {
869 Log.d(LOGTAG,
870 account.getJid()
871 + ": could not encrypt message to "
872 + message.getCounterpart());
873 }
874 packet.addChild("private", "urn:xmpp:carbons:2");
875 packet.addChild("no-copy", "urn:xmpp:hints");
876 packet.setTo(otrSession.getSessionID().getAccountID() + "/"
877 + otrSession.getSessionID().getUserID());
878 packet.setFrom(account.getFullJid());
879 } else {
880 packet.setBody(message.getBody());
881 packet.setTo(message.getCounterpart());
882 packet.setFrom(account.getJid());
883 }
884 } else if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
885 packet.setType(MessagePacket.TYPE_GROUPCHAT);
886 packet.setBody(message.getBody());
887 packet.setTo(message.getCounterpart().split("/")[0]);
888 packet.setFrom(account.getJid());
889 }
890 packet.setId(message.getUuid());
891 return packet;
892 }
893
894 private void getRoster(Account account,
895 final OnRosterFetchedListener listener) {
896 List<Contact> contacts = databaseBackend.getContactsByAccount(account);
897 for (int i = 0; i < contacts.size(); ++i) {
898 contacts.get(i).setAccount(account);
899 }
900 if (listener != null) {
901 listener.onRosterFetched(contacts);
902 }
903 }
904
905 public List<Contact> getRoster(Account account) {
906 List<Contact> contacts = databaseBackend.getContactsByAccount(account);
907 for (int i = 0; i < contacts.size(); ++i) {
908 contacts.get(i).setAccount(account);
909 }
910 return contacts;
911 }
912
913 public void updateRoster(final Account account,
914 final OnRosterFetchedListener listener) {
915 IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
916 if (!"".equals(account.getRosterVersion())) {
917 Log.d(LOGTAG, account.getJid() + ": fetching roster version "
918 + account.getRosterVersion());
919 } else {
920 Log.d(LOGTAG, account.getJid() + ": fetching roster");
921 }
922 iqPacket.query("jabber:iq:roster").setAttribute("ver",
923 account.getRosterVersion());
924 account.getXmppConnection().sendIqPacket(iqPacket,
925 new OnIqPacketReceived() {
926
927 @Override
928 public void onIqPacketReceived(final Account account,
929 IqPacket packet) {
930 Element roster = packet.findChild("query");
931 if (roster != null) {
932 Log.d(LOGTAG, account.getJid()
933 + ": processing roster");
934 processRosterItems(account, roster);
935 StringBuilder mWhere = new StringBuilder();
936 mWhere.append("jid NOT IN(");
937 List<Element> items = roster.getChildren();
938 for (int i = 0; i < items.size(); ++i) {
939 mWhere.append(DatabaseUtils
940 .sqlEscapeString(items.get(i)
941 .getAttribute("jid")));
942 if (i != items.size() - 1) {
943 mWhere.append(",");
944 }
945 }
946 mWhere.append(") and accountUuid = \"");
947 mWhere.append(account.getUuid());
948 mWhere.append("\"");
949 List<Contact> contactsToDelete = databaseBackend
950 .getContacts(mWhere.toString());
951 for (Contact contact : contactsToDelete) {
952 databaseBackend.deleteContact(contact);
953 replaceContactInConversation(account,
954 contact.getJid(), null);
955 }
956
957 } else {
958 Log.d(LOGTAG, account.getJid()
959 + ": empty roster returend");
960 }
961 mergePhoneContactsWithRoster(new OnPhoneContactsMerged() {
962
963 @Override
964 public void phoneContactsMerged() {
965 if (listener != null) {
966 getRoster(account, listener);
967 }
968 }
969 });
970 }
971 });
972 }
973
974 public void mergePhoneContactsWithRoster(
975 final OnPhoneContactsMerged listener) {
976 PhoneHelper.loadPhoneContacts(getApplicationContext(),
977 new OnPhoneContactsLoadedListener() {
978 @Override
979 public void onPhoneContactsLoaded(
980 Hashtable<String, Bundle> phoneContacts) {
981 List<Contact> contacts = databaseBackend
982 .getContactsByAccount(null);
983 for (int i = 0; i < contacts.size(); ++i) {
984 Contact contact = contacts.get(i);
985 if (phoneContacts.containsKey(contact.getJid())) {
986 Bundle phoneContact = phoneContacts.get(contact
987 .getJid());
988 String systemAccount = phoneContact
989 .getInt("phoneid")
990 + "#"
991 + phoneContact.getString("lookup");
992 contact.setSystemAccount(systemAccount);
993 contact.setPhotoUri(phoneContact
994 .getString("photouri"));
995 contact.setDisplayName(phoneContact
996 .getString("displayname"));
997 databaseBackend.updateContact(contact, false);
998 replaceContactInConversation(
999 contact.getAccount(), contact.getJid(),
1000 contact);
1001 } else {
1002 if ((contact.getSystemAccount() != null)
1003 || (contact.getProfilePhoto() != null)) {
1004 contact.setSystemAccount(null);
1005 contact.setPhotoUri(null);
1006 databaseBackend.updateContact(contact,
1007 false);
1008 replaceContactInConversation(
1009 contact.getAccount(),
1010 contact.getJid(), contact);
1011 }
1012 }
1013 }
1014 if (listener != null) {
1015 listener.phoneContactsMerged();
1016 }
1017 }
1018 });
1019 }
1020
1021 public List<Conversation> getConversations() {
1022 if (this.conversations == null) {
1023 Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
1024 for (Account account : this.accounts) {
1025 accountLookupTable.put(account.getUuid(), account);
1026 }
1027 this.conversations = databaseBackend
1028 .getConversations(Conversation.STATUS_AVAILABLE);
1029 for (Conversation conv : this.conversations) {
1030 Account account = accountLookupTable.get(conv.getAccountUuid());
1031 conv.setAccount(account);
1032 conv.setContact(findContact(account, conv.getContactJid()));
1033 conv.setMessages(databaseBackend.getMessages(conv, 50));
1034 }
1035 }
1036 Collections.sort(this.conversations, new Comparator<Conversation>() {
1037 @Override
1038 public int compare(Conversation lhs, Conversation rhs) {
1039 return (int) (rhs.getLatestMessage().getTimeSent() - lhs
1040 .getLatestMessage().getTimeSent());
1041 }
1042 });
1043 return this.conversations;
1044 }
1045
1046 public List<Account> getAccounts() {
1047 return this.accounts;
1048 }
1049
1050 public Contact findContact(Account account, String jid) {
1051 Contact contact = databaseBackend.findContact(account, jid);
1052 if (contact != null) {
1053 contact.setAccount(account);
1054 }
1055 return contact;
1056 }
1057
1058 public Conversation findOrCreateConversation(Account account, String jid,
1059 boolean muc) {
1060 for (Conversation conv : this.getConversations()) {
1061 if ((conv.getAccount().equals(account))
1062 && (conv.getContactJid().split("/")[0].equals(jid))) {
1063 return conv;
1064 }
1065 }
1066 Conversation conversation = databaseBackend.findConversation(account,
1067 jid);
1068 if (conversation != null) {
1069 conversation.setStatus(Conversation.STATUS_AVAILABLE);
1070 conversation.setAccount(account);
1071 if (muc) {
1072 conversation.setMode(Conversation.MODE_MULTI);
1073 } else {
1074 conversation.setMode(Conversation.MODE_SINGLE);
1075 }
1076 conversation.setMessages(databaseBackend.getMessages(conversation,
1077 50));
1078 this.databaseBackend.updateConversation(conversation);
1079 conversation.setContact(findContact(account,
1080 conversation.getContactJid()));
1081 } else {
1082 String conversationName;
1083 Contact contact = findContact(account, jid);
1084 if (contact != null) {
1085 conversationName = contact.getDisplayName();
1086 } else {
1087 conversationName = jid.split("@")[0];
1088 }
1089 if (muc) {
1090 conversation = new Conversation(conversationName, account, jid,
1091 Conversation.MODE_MULTI);
1092 } else {
1093 conversation = new Conversation(conversationName, account, jid,
1094 Conversation.MODE_SINGLE);
1095 }
1096 conversation.setContact(contact);
1097 this.databaseBackend.createConversation(conversation);
1098 }
1099 this.conversations.add(conversation);
1100 if ((account.getStatus() == Account.STATUS_ONLINE)
1101 && (conversation.getMode() == Conversation.MODE_MULTI)) {
1102 joinMuc(conversation);
1103 }
1104 if (this.convChangedListener != null) {
1105 this.convChangedListener.onConversationListChanged();
1106 }
1107 return conversation;
1108 }
1109
1110 public void archiveConversation(Conversation conversation) {
1111 if (conversation.getMode() == Conversation.MODE_MULTI) {
1112 leaveMuc(conversation);
1113 } else {
1114 conversation.endOtrIfNeeded();
1115 }
1116 this.databaseBackend.updateConversation(conversation);
1117 this.conversations.remove(conversation);
1118 if (this.convChangedListener != null) {
1119 this.convChangedListener.onConversationListChanged();
1120 }
1121 }
1122
1123 public void clearConversationHistory(Conversation conversation) {
1124 this.databaseBackend.deleteMessagesInConversation(conversation);
1125 this.fileBackend.removeFiles(conversation);
1126 conversation.getMessages().clear();
1127 if (this.convChangedListener != null) {
1128 this.convChangedListener.onConversationListChanged();
1129 }
1130 }
1131
1132 public int getConversationCount() {
1133 return this.databaseBackend.getConversationCount();
1134 }
1135
1136 public void createAccount(Account account) {
1137 databaseBackend.createAccount(account);
1138 this.accounts.add(account);
1139 this.reconnectAccount(account, false);
1140 if (accountChangedListener != null)
1141 accountChangedListener.onAccountListChangedListener();
1142 }
1143
1144 public void deleteContact(Contact contact) {
1145 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
1146 Element query = iq.query("jabber:iq:roster");
1147 query.addChild("item").setAttribute("jid", contact.getJid())
1148 .setAttribute("subscription", "remove");
1149 contact.getAccount().getXmppConnection().sendIqPacket(iq, null);
1150 replaceContactInConversation(contact.getAccount(), contact.getJid(),
1151 null);
1152 databaseBackend.deleteContact(contact);
1153 }
1154
1155 public void updateAccount(Account account) {
1156 this.statusListener.onStatusChanged(account);
1157 databaseBackend.updateAccount(account);
1158 reconnectAccount(account, false);
1159 if (accountChangedListener != null) {
1160 accountChangedListener.onAccountListChangedListener();
1161 }
1162 UIHelper.showErrorNotification(getApplicationContext(),
1163 getAccounts());
1164 }
1165
1166 public void deleteAccount(Account account) {
1167 if (account.getXmppConnection() != null) {
1168 this.disconnect(account, true);
1169 }
1170 databaseBackend.deleteAccount(account);
1171 this.accounts.remove(account);
1172 if (accountChangedListener != null) {
1173 accountChangedListener.onAccountListChangedListener();
1174 }
1175 UIHelper.showErrorNotification(getApplicationContext(),
1176 getAccounts());
1177 }
1178
1179 public void setOnConversationListChangedListener(
1180 OnConversationListChangedListener listener) {
1181 this.convChangedListener = listener;
1182 this.convChangedListenerCount++;
1183 }
1184
1185 public void removeOnConversationListChangedListener() {
1186 this.convChangedListenerCount--;
1187 if (this.convChangedListenerCount == 0) {
1188 this.convChangedListener = null;
1189 }
1190 }
1191
1192 public void setOnAccountListChangedListener(
1193 OnAccountListChangedListener listener) {
1194 this.accountChangedListener = listener;
1195 }
1196
1197 public void removeOnAccountListChangedListener() {
1198 this.accountChangedListener = null;
1199 }
1200
1201 public void connectMultiModeConversations(Account account) {
1202 List<Conversation> conversations = getConversations();
1203 for (int i = 0; i < conversations.size(); i++) {
1204 Conversation conversation = conversations.get(i);
1205 if ((conversation.getMode() == Conversation.MODE_MULTI)
1206 && (conversation.getAccount() == account)) {
1207 joinMuc(conversation);
1208 }
1209 }
1210 }
1211
1212 public void joinMuc(Conversation conversation) {
1213 String[] mucParts = conversation.getContactJid().split("/");
1214 String muc;
1215 String nick;
1216 if (mucParts.length == 2) {
1217 muc = mucParts[0];
1218 nick = mucParts[1];
1219 } else {
1220 muc = mucParts[0];
1221 nick = conversation.getAccount().getUsername();
1222 }
1223 PresencePacket packet = new PresencePacket();
1224 packet.setAttribute("to", muc + "/" + nick);
1225 Element x = new Element("x");
1226 x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
1227 if (conversation.getMessages().size() != 0) {
1228 long lastMsgTime = conversation.getLatestMessage().getTimeSent();
1229 long diff = (System.currentTimeMillis() - lastMsgTime) / 1000 - 1;
1230 x.addChild("history").setAttribute("seconds", diff + "");
1231 }
1232 packet.addChild(x);
1233 conversation.getAccount().getXmppConnection()
1234 .sendPresencePacket(packet);
1235 }
1236
1237 private OnRenameListener renameListener = null;
1238
1239 public void setOnRenameListener(OnRenameListener listener) {
1240 this.renameListener = listener;
1241 }
1242
1243 public void renameInMuc(final Conversation conversation, final String nick) {
1244 final MucOptions options = conversation.getMucOptions();
1245 if (options.online()) {
1246 options.setOnRenameListener(new OnRenameListener() {
1247
1248 @Override
1249 public void onRename(boolean success) {
1250 if (renameListener != null) {
1251 renameListener.onRename(success);
1252 }
1253 if (success) {
1254 String jid = conversation.getContactJid().split("/")[0]
1255 + "/" + nick;
1256 conversation.setContactJid(jid);
1257 databaseBackend.updateConversation(conversation);
1258 }
1259 }
1260 });
1261 options.flagAboutToRename();
1262 PresencePacket packet = new PresencePacket();
1263 packet.setAttribute("to",
1264 conversation.getContactJid().split("/")[0] + "/" + nick);
1265 packet.setAttribute("from", conversation.getAccount().getFullJid());
1266
1267 conversation.getAccount().getXmppConnection()
1268 .sendPresencePacket(packet, null);
1269 } else {
1270 String jid = conversation.getContactJid().split("/")[0] + "/"
1271 + nick;
1272 conversation.setContactJid(jid);
1273 databaseBackend.updateConversation(conversation);
1274 if (conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
1275 joinMuc(conversation);
1276 }
1277 }
1278 }
1279
1280 public void leaveMuc(Conversation conversation) {
1281 PresencePacket packet = new PresencePacket();
1282 packet.setAttribute("to", conversation.getContactJid().split("/")[0]
1283 + "/" + conversation.getMucOptions().getNick());
1284 packet.setAttribute("from", conversation.getAccount().getFullJid());
1285 packet.setAttribute("type", "unavailable");
1286 Log.d(LOGTAG, "send leaving muc " + packet);
1287 conversation.getAccount().getXmppConnection()
1288 .sendPresencePacket(packet);
1289 conversation.getMucOptions().setOffline();
1290 }
1291
1292 public void disconnect(Account account, boolean force) {
1293 if ((account.getStatus() == Account.STATUS_ONLINE)
1294 || (account.getStatus() == Account.STATUS_DISABLED)) {
1295 if (!force) {
1296 List<Conversation> conversations = getConversations();
1297 for (int i = 0; i < conversations.size(); i++) {
1298 Conversation conversation = conversations.get(i);
1299 if (conversation.getAccount() == account) {
1300 if (conversation.getMode() == Conversation.MODE_MULTI) {
1301 leaveMuc(conversation);
1302 } else {
1303 conversation.endOtrIfNeeded();
1304 }
1305 }
1306 }
1307 }
1308 account.getXmppConnection().disconnect(force);
1309 }
1310 }
1311
1312 @Override
1313 public IBinder onBind(Intent intent) {
1314 return mBinder;
1315 }
1316
1317 public void updateContact(Contact contact) {
1318 databaseBackend.updateContact(contact, false);
1319 replaceContactInConversation(contact.getAccount(), contact.getJid(),
1320 contact);
1321 }
1322
1323 public void updateMessage(Message message) {
1324 databaseBackend.updateMessage(message);
1325 }
1326
1327 public void createContact(Contact contact) {
1328 SharedPreferences sharedPref = getPreferences();
1329 boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
1330 if (autoGrant) {
1331 contact.setSubscriptionOption(Contact.Subscription.PREEMPTIVE_GRANT);
1332 contact.setSubscriptionOption(Contact.Subscription.ASKING);
1333 }
1334 databaseBackend.createContact(contact);
1335 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
1336 Element query = new Element("query");
1337 query.setAttribute("xmlns", "jabber:iq:roster");
1338 Element item = new Element("item");
1339 item.setAttribute("jid", contact.getJid());
1340 item.setAttribute("name", contact.getJid());
1341 query.addChild(item);
1342 iq.addChild(query);
1343 Account account = contact.getAccount();
1344 account.getXmppConnection().sendIqPacket(iq, null);
1345 if (autoGrant) {
1346 requestPresenceUpdatesFrom(contact);
1347 if (account.getXmppConnection().hasPendingSubscription(
1348 contact.getJid())) {
1349 Log.d("xmppService", "contact had pending subscription");
1350 sendPresenceUpdatesTo(contact);
1351 }
1352 }
1353 replaceContactInConversation(contact.getAccount(), contact.getJid(),
1354 contact);
1355 }
1356
1357 public void requestPresenceUpdatesFrom(Contact contact) {
1358 // Requesting a Subscription type=subscribe
1359 PresencePacket packet = new PresencePacket();
1360 packet.setAttribute("type", "subscribe");
1361 packet.setAttribute("to", contact.getJid());
1362 packet.setAttribute("from", contact.getAccount().getJid());
1363 Log.d(LOGTAG, packet.toString());
1364 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
1365 }
1366
1367 public void stopPresenceUpdatesFrom(Contact contact) {
1368 // Unsubscribing type='unsubscribe'
1369 PresencePacket packet = new PresencePacket();
1370 packet.setAttribute("type", "unsubscribe");
1371 packet.setAttribute("to", contact.getJid());
1372 packet.setAttribute("from", contact.getAccount().getJid());
1373 Log.d(LOGTAG, packet.toString());
1374 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
1375 }
1376
1377 public void stopPresenceUpdatesTo(Contact contact) {
1378 // Canceling a Subscription type=unsubscribed
1379 PresencePacket packet = new PresencePacket();
1380 packet.setAttribute("type", "unsubscribed");
1381 packet.setAttribute("to", contact.getJid());
1382 packet.setAttribute("from", contact.getAccount().getJid());
1383 Log.d(LOGTAG, packet.toString());
1384 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
1385 }
1386
1387 public void sendPresenceUpdatesTo(Contact contact) {
1388 // type='subscribed'
1389 PresencePacket packet = new PresencePacket();
1390 packet.setAttribute("type", "subscribed");
1391 packet.setAttribute("to", contact.getJid());
1392 packet.setAttribute("from", contact.getAccount().getJid());
1393 Log.d(LOGTAG, packet.toString());
1394 contact.getAccount().getXmppConnection().sendPresencePacket(packet);
1395 }
1396
1397 public void sendPgpPresence(Account account, String signature) {
1398 PresencePacket packet = new PresencePacket();
1399 packet.setAttribute("from", account.getFullJid());
1400 Element status = new Element("status");
1401 status.setContent("online");
1402 packet.addChild(status);
1403 Element x = new Element("x");
1404 x.setAttribute("xmlns", "jabber:x:signed");
1405 x.setContent(signature);
1406 packet.addChild(x);
1407 account.getXmppConnection().sendPresencePacket(packet);
1408 }
1409
1410 public void updateConversation(Conversation conversation) {
1411 this.databaseBackend.updateConversation(conversation);
1412 }
1413
1414 public Contact findContact(String uuid) {
1415 Contact contact = this.databaseBackend.getContact(uuid);
1416 if (contact != null) {
1417 for (Account account : getAccounts()) {
1418 if (contact.getAccountUuid().equals(account.getUuid())) {
1419 contact.setAccount(account);
1420 }
1421 }
1422 }
1423 return contact;
1424 }
1425
1426 public void removeOnTLSExceptionReceivedListener() {
1427 this.tlsException = null;
1428 }
1429
1430 // TODO dont let thread sleep but schedule wake up
1431 public void reconnectAccount(final Account account, final boolean force) {
1432 new Thread(new Runnable() {
1433
1434 @Override
1435 public void run() {
1436 if (account.getXmppConnection() != null) {
1437 disconnect(account, force);
1438 }
1439 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
1440 if (account.getXmppConnection() == null) {
1441 account.setXmppConnection(createConnection(account));
1442 }
1443 Thread thread = new Thread(account.getXmppConnection());
1444 thread.start();
1445 scheduleWakeupCall((int) (CONNECT_TIMEOUT * 1.2), false);
1446 }
1447 }
1448 }).start();
1449 }
1450
1451 public void sendConversationSubject(Conversation conversation,
1452 String subject) {
1453 MessagePacket packet = new MessagePacket();
1454 packet.setType(MessagePacket.TYPE_GROUPCHAT);
1455 packet.setTo(conversation.getContactJid().split("/")[0]);
1456 Element subjectChild = new Element("subject");
1457 subjectChild.setContent(subject);
1458 packet.addChild(subjectChild);
1459 packet.setFrom(conversation.getAccount().getJid());
1460 Account account = conversation.getAccount();
1461 if (account.getStatus() == Account.STATUS_ONLINE) {
1462 account.getXmppConnection().sendMessagePacket(packet);
1463 }
1464 }
1465
1466 public void inviteToConference(Conversation conversation,
1467 List<Contact> contacts) {
1468 for (Contact contact : contacts) {
1469 MessagePacket packet = new MessagePacket();
1470 packet.setTo(conversation.getContactJid().split("/")[0]);
1471 packet.setFrom(conversation.getAccount().getFullJid());
1472 Element x = new Element("x");
1473 x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user");
1474 Element invite = new Element("invite");
1475 invite.setAttribute("to", contact.getJid());
1476 x.addChild(invite);
1477 packet.addChild(x);
1478 Log.d(LOGTAG, packet.toString());
1479 conversation.getAccount().getXmppConnection()
1480 .sendMessagePacket(packet);
1481 }
1482
1483 }
1484
1485 public boolean markMessage(Account account, String recipient, String uuid,
1486 int status) {
1487 for (Conversation conversation : getConversations()) {
1488 if (conversation.getContactJid().equals(recipient)
1489 && conversation.getAccount().equals(account)) {
1490 return markMessage(conversation, uuid, status);
1491 }
1492 }
1493 return false;
1494 }
1495
1496 public boolean markMessage(Conversation conversation, String uuid, int status) {
1497 for (Message message : conversation.getMessages()) {
1498 if (message.getUuid().equals(uuid)) {
1499 markMessage(message, status);
1500 return true;
1501 }
1502 }
1503 return false;
1504 }
1505
1506 public void markMessage(Message message, int status) {
1507 message.setStatus(status);
1508 databaseBackend.updateMessage(message);
1509 if (convChangedListener != null) {
1510 convChangedListener.onConversationListChanged();
1511 }
1512 }
1513
1514 public SharedPreferences getPreferences() {
1515 return PreferenceManager
1516 .getDefaultSharedPreferences(getApplicationContext());
1517 }
1518
1519 public void updateUi(Conversation conversation, boolean notify) {
1520 if (convChangedListener != null) {
1521 convChangedListener.onConversationListChanged();
1522 } else {
1523 UIHelper.updateNotification(getApplicationContext(),
1524 getConversations(), conversation, notify);
1525 }
1526 }
1527}