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