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