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