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