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