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