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