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;
13import java.util.concurrent.CopyOnWriteArrayList;
14
15import org.openintents.openpgp.util.OpenPgpApi;
16import org.openintents.openpgp.util.OpenPgpServiceConnection;
17
18import de.duenndns.ssl.MemorizingTrustManager;
19
20import net.java.otr4j.OtrException;
21import net.java.otr4j.session.Session;
22import net.java.otr4j.session.SessionStatus;
23import eu.siacs.conversations.Config;
24import eu.siacs.conversations.R;
25import eu.siacs.conversations.crypto.PgpEngine;
26import eu.siacs.conversations.entities.Account;
27import eu.siacs.conversations.entities.Bookmark;
28import eu.siacs.conversations.entities.Contact;
29import eu.siacs.conversations.entities.Conversation;
30import eu.siacs.conversations.entities.Message;
31import eu.siacs.conversations.entities.MucOptions;
32import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
33import eu.siacs.conversations.entities.Presences;
34import eu.siacs.conversations.generator.IqGenerator;
35import eu.siacs.conversations.generator.MessageGenerator;
36import eu.siacs.conversations.generator.PresenceGenerator;
37import eu.siacs.conversations.parser.IqParser;
38import eu.siacs.conversations.parser.MessageParser;
39import eu.siacs.conversations.parser.PresenceParser;
40import eu.siacs.conversations.persistance.DatabaseBackend;
41import eu.siacs.conversations.persistance.FileBackend;
42import eu.siacs.conversations.ui.UiCallback;
43import eu.siacs.conversations.utils.CryptoHelper;
44import eu.siacs.conversations.utils.ExceptionHelper;
45import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
46import eu.siacs.conversations.utils.PRNGFixes;
47import eu.siacs.conversations.utils.PhoneHelper;
48import eu.siacs.conversations.utils.UIHelper;
49import eu.siacs.conversations.xml.Element;
50import eu.siacs.conversations.xmpp.OnBindListener;
51import eu.siacs.conversations.xmpp.OnContactStatusChanged;
52import eu.siacs.conversations.xmpp.OnIqPacketReceived;
53import eu.siacs.conversations.xmpp.OnMessageAcknowledged;
54import eu.siacs.conversations.xmpp.OnStatusChanged;
55import eu.siacs.conversations.xmpp.XmppConnection;
56import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
57import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
58import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
59import eu.siacs.conversations.xmpp.pep.Avatar;
60import eu.siacs.conversations.xmpp.stanzas.IqPacket;
61import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
62import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
63import android.annotation.SuppressLint;
64import android.app.AlarmManager;
65import android.app.PendingIntent;
66import android.app.Service;
67import android.content.Context;
68import android.content.Intent;
69import android.content.SharedPreferences;
70import android.database.ContentObserver;
71import android.graphics.Bitmap;
72import android.net.ConnectivityManager;
73import android.net.NetworkInfo;
74import android.net.Uri;
75import android.os.Binder;
76import android.os.Bundle;
77import android.os.IBinder;
78import android.os.PowerManager;
79import android.os.PowerManager.WakeLock;
80import android.os.SystemClock;
81import android.preference.PreferenceManager;
82import android.provider.ContactsContract;
83import android.util.Log;
84
85public class XmppConnectionService extends Service {
86
87 public DatabaseBackend databaseBackend;
88 private FileBackend fileBackend;
89
90 public long startDate;
91
92 private static String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
93
94 private MemorizingTrustManager mMemorizingTrustManager;
95
96 private NotificationService mNotificationService;
97
98 private MessageParser mMessageParser = new MessageParser(this);
99 private PresenceParser mPresenceParser = new PresenceParser(this);
100 private IqParser mIqParser = new IqParser(this);
101 private MessageGenerator mMessageGenerator = new MessageGenerator(this);
102 private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this);
103
104 private List<Account> accounts;
105 private CopyOnWriteArrayList<Conversation> conversations = null;
106 private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
107 this);
108
109 private OnConversationUpdate mOnConversationUpdate = null;
110 private int convChangedListenerCount = 0;
111 private OnAccountUpdate mOnAccountUpdate = null;
112 private int accountChangedListenerCount = 0;
113 private OnRosterUpdate mOnRosterUpdate = null;
114 private int rosterChangedListenerCount = 0;
115 public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() {
116
117 @Override
118 public void onContactStatusChanged(Contact contact, boolean online) {
119 Conversation conversation = find(getConversations(), contact);
120 if (conversation != null) {
121 conversation.endOtrIfNeeded();
122 if (online && (contact.getPresences().size() == 1)) {
123 sendUnsendMessages(conversation);
124 }
125 }
126 }
127 };
128
129 private SecureRandom mRandom;
130
131 private ContentObserver contactObserver = new ContentObserver(null) {
132 @Override
133 public void onChange(boolean selfChange) {
134 super.onChange(selfChange);
135 Intent intent = new Intent(getApplicationContext(),
136 XmppConnectionService.class);
137 intent.setAction(ACTION_MERGE_PHONE_CONTACTS);
138 startService(intent);
139 }
140 };
141
142 private final IBinder mBinder = new XmppConnectionBinder();
143 private OnStatusChanged statusListener = new OnStatusChanged() {
144
145 @Override
146 public void onStatusChanged(Account account) {
147 XmppConnection connection = account.getXmppConnection();
148 if (mOnAccountUpdate != null) {
149 mOnAccountUpdate.onAccountUpdate();
150 ;
151 }
152 if (account.getStatus() == Account.STATUS_ONLINE) {
153 for (Conversation conversation : account.pendingConferenceLeaves) {
154 leaveMuc(conversation);
155 }
156 for (Conversation conversation : account.pendingConferenceJoins) {
157 joinMuc(conversation);
158 }
159 mJingleConnectionManager.cancelInTransmission();
160 List<Conversation> conversations = getConversations();
161 for (int i = 0; i < conversations.size(); ++i) {
162 if (conversations.get(i).getAccount() == account) {
163 conversations.get(i).startOtrIfNeeded();
164 sendUnsendMessages(conversations.get(i));
165 }
166 }
167 if (connection != null && connection.getFeatures().csi()) {
168 if (checkListeners()) {
169 Log.d(Config.LOGTAG, account.getJid()
170 + " sending csi//inactive");
171 connection.sendInactive();
172 } else {
173 Log.d(Config.LOGTAG, account.getJid()
174 + " sending csi//active");
175 connection.sendActive();
176 }
177 }
178 syncDirtyContacts(account);
179 scheduleWakeupCall(Config.PING_MAX_INTERVAL, true);
180 } else if (account.getStatus() == Account.STATUS_OFFLINE) {
181 resetSendingToWaiting(account);
182 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
183 int timeToReconnect = mRandom.nextInt(50) + 10;
184 scheduleWakeupCall(timeToReconnect, false);
185 }
186 } else if (account.getStatus() == Account.STATUS_REGISTRATION_SUCCESSFULL) {
187 databaseBackend.updateAccount(account);
188 reconnectAccount(account, true);
189 } else if ((account.getStatus() != Account.STATUS_CONNECTING)
190 && (account.getStatus() != Account.STATUS_NO_INTERNET)) {
191 if (connection != null) {
192 int next = connection.getTimeToNextAttempt();
193 Log.d(Config.LOGTAG, account.getJid()
194 + ": error connecting account. try again in "
195 + next + "s for the "
196 + (connection.getAttempt() + 1) + " time");
197 scheduleWakeupCall((int) (next * 1.2), false);
198 }
199 }
200 UIHelper.showErrorNotification(getApplicationContext(),
201 getAccounts());
202 }
203 };
204
205 private OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() {
206
207 @Override
208 public void onJinglePacketReceived(Account account, JinglePacket packet) {
209 mJingleConnectionManager.deliverPacket(account, packet);
210 }
211 };
212
213 private OpenPgpServiceConnection pgpServiceConnection;
214 private PgpEngine mPgpEngine = null;
215 private Intent pingIntent;
216 private PendingIntent pendingPingIntent = null;
217 private WakeLock wakeLock;
218 private PowerManager pm;
219 private OnBindListener mOnBindListener = new OnBindListener() {
220
221 @Override
222 public void onBind(final Account account) {
223 account.getRoster().clearPresences();
224 account.clearPresences(); // self presences
225 account.pendingConferenceJoins.clear();
226 account.pendingConferenceLeaves.clear();
227 fetchRosterFromServer(account);
228 fetchBookmarks(account);
229 sendPresencePacket(account,
230 mPresenceGenerator.sendPresence(account));
231 connectMultiModeConversations(account);
232 updateConversationUi();
233 }
234 };
235
236 private OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() {
237
238 @Override
239 public void onMessageAcknowledged(Account account, String uuid) {
240 for (Conversation conversation : getConversations()) {
241 if (conversation.getAccount() == account) {
242 for (Message message : conversation.getMessages()) {
243 if ((message.getStatus() == Message.STATUS_UNSEND || message
244 .getStatus() == Message.STATUS_WAITING)
245 && message.getUuid().equals(uuid)) {
246 markMessage(message, Message.STATUS_SEND);
247 return;
248 }
249 }
250 }
251 }
252 }
253 };
254
255 public PgpEngine getPgpEngine() {
256 if (pgpServiceConnection.isBound()) {
257 if (this.mPgpEngine == null) {
258 this.mPgpEngine = new PgpEngine(new OpenPgpApi(
259 getApplicationContext(),
260 pgpServiceConnection.getService()), this);
261 }
262 return mPgpEngine;
263 } else {
264 return null;
265 }
266
267 }
268
269 public FileBackend getFileBackend() {
270 return this.fileBackend;
271 }
272
273 public Message attachImageToConversation(final Conversation conversation,
274 final Uri uri, final UiCallback<Message> callback) {
275 final Message message;
276 if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
277 message = new Message(conversation, "",
278 Message.ENCRYPTION_DECRYPTED);
279 } else {
280 message = new Message(conversation, "",
281 conversation.getNextEncryption(forceEncryption()));
282 }
283 message.setPresence(conversation.getNextPresence());
284 message.setType(Message.TYPE_IMAGE);
285 message.setStatus(Message.STATUS_OFFERED);
286 new Thread(new Runnable() {
287
288 @Override
289 public void run() {
290 try {
291 getFileBackend().copyImageToPrivateStorage(message, uri);
292 if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
293 getPgpEngine().encrypt(message, callback);
294 } else {
295 callback.success(message);
296 }
297 } catch (FileBackend.ImageCopyException e) {
298 callback.error(e.getResId(), message);
299 }
300 }
301 }).start();
302 return message;
303 }
304
305 public Conversation find(Bookmark bookmark) {
306 return find(bookmark.getAccount(), bookmark.getJid());
307 }
308
309 public Conversation find(Account account, String jid) {
310 return find(getConversations(), account, jid);
311 }
312
313 public class XmppConnectionBinder extends Binder {
314 public XmppConnectionService getService() {
315 return XmppConnectionService.this;
316 }
317 }
318
319 @Override
320 public int onStartCommand(Intent intent, int flags, int startId) {
321 if ((intent != null)
322 && (ACTION_MERGE_PHONE_CONTACTS.equals(intent.getAction()))) {
323 mergePhoneContactsWithRoster();
324 return START_STICKY;
325 } else if ((intent != null)
326 && (Intent.ACTION_SHUTDOWN.equals(intent.getAction()))) {
327 logoutAndSave();
328 return START_NOT_STICKY;
329 }
330 this.wakeLock.acquire();
331 ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
332 .getSystemService(Context.CONNECTIVITY_SERVICE);
333 NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
334 boolean isConnected = activeNetwork != null
335 && activeNetwork.isConnected();
336
337 for (Account account : accounts) {
338 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
339 if (!isConnected) {
340 account.setStatus(Account.STATUS_NO_INTERNET);
341 if (statusListener != null) {
342 statusListener.onStatusChanged(account);
343 }
344 } else {
345 if (account.getStatus() == Account.STATUS_NO_INTERNET) {
346 account.setStatus(Account.STATUS_OFFLINE);
347 if (statusListener != null) {
348 statusListener.onStatusChanged(account);
349 }
350 }
351 if (account.getStatus() == Account.STATUS_ONLINE) {
352 long lastReceived = account.getXmppConnection()
353 .getLastPacketReceived();
354 long lastSent = account.getXmppConnection()
355 .getLastPingSent();
356 if (lastSent - lastReceived >= Config.PING_TIMEOUT * 1000) {
357 Log.d(Config.LOGTAG, account.getJid()
358 + ": ping timeout");
359 this.reconnectAccount(account, true);
360 } else if (SystemClock.elapsedRealtime() - lastReceived >= Config.PING_MIN_INTERVAL * 1000) {
361 account.getXmppConnection().sendPing();
362 this.scheduleWakeupCall(2, false);
363 }
364 } else if (account.getStatus() == Account.STATUS_OFFLINE) {
365 if (account.getXmppConnection() == null) {
366 account.setXmppConnection(this
367 .createConnection(account));
368 }
369 new Thread(account.getXmppConnection()).start();
370 } else if ((account.getStatus() == Account.STATUS_CONNECTING)
371 && ((SystemClock.elapsedRealtime() - account
372 .getXmppConnection().getLastConnect()) / 1000 >= Config.CONNECT_TIMEOUT)) {
373 Log.d(Config.LOGTAG, account.getJid()
374 + ": time out during connect reconnecting");
375 reconnectAccount(account, true);
376 } else {
377 if (account.getXmppConnection().getTimeToNextAttempt() <= 0) {
378 reconnectAccount(account, true);
379 }
380 }
381 // in any case. reschedule wakup call
382 this.scheduleWakeupCall(Config.PING_MAX_INTERVAL, true);
383 }
384 if (mOnAccountUpdate != null) {
385 mOnAccountUpdate.onAccountUpdate();
386 }
387 }
388 }
389 if (wakeLock.isHeld()) {
390 try {
391 wakeLock.release();
392 } catch (RuntimeException re) {
393 }
394 }
395 return START_STICKY;
396 }
397
398 @SuppressLint("TrulyRandom")
399 @Override
400 public void onCreate() {
401 ExceptionHelper.init(getApplicationContext());
402 PRNGFixes.apply();
403 this.mRandom = new SecureRandom();
404 this.mMemorizingTrustManager = new MemorizingTrustManager(
405 getApplicationContext());
406 this.mNotificationService = new NotificationService(this);
407 this.databaseBackend = DatabaseBackend
408 .getInstance(getApplicationContext());
409 this.fileBackend = new FileBackend(getApplicationContext());
410 this.accounts = databaseBackend.getAccounts();
411
412 for (Account account : this.accounts) {
413 this.databaseBackend.readRoster(account.getRoster());
414 }
415 this.mergePhoneContactsWithRoster();
416 this.getConversations();
417
418 getContentResolver().registerContentObserver(
419 ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
420 this.pgpServiceConnection = new OpenPgpServiceConnection(
421 getApplicationContext(), "org.sufficientlysecure.keychain");
422 this.pgpServiceConnection.bindToService();
423
424 this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
425 this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
426 "XmppConnectionService");
427 }
428
429 @Override
430 public void onDestroy() {
431 super.onDestroy();
432 this.logoutAndSave();
433 }
434
435 @Override
436 public void onTaskRemoved(Intent rootIntent) {
437 super.onTaskRemoved(rootIntent);
438 this.logoutAndSave();
439 }
440
441 private void logoutAndSave() {
442 for (Account account : accounts) {
443 databaseBackend.writeRoster(account.getRoster());
444 if (account.getXmppConnection() != null) {
445 disconnect(account, false);
446 }
447 }
448 Context context = getApplicationContext();
449 AlarmManager alarmManager = (AlarmManager) context
450 .getSystemService(Context.ALARM_SERVICE);
451 Intent intent = new Intent(context, EventReceiver.class);
452 alarmManager.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
453 Log.d(Config.LOGTAG, "good bye");
454 stopSelf();
455 }
456
457 protected void scheduleWakeupCall(int seconds, boolean ping) {
458 long timeToWake = SystemClock.elapsedRealtime() + seconds * 1000;
459 Context context = getApplicationContext();
460 AlarmManager alarmManager = (AlarmManager) context
461 .getSystemService(Context.ALARM_SERVICE);
462
463 if (ping) {
464 if (this.pingIntent == null) {
465 this.pingIntent = new Intent(context, EventReceiver.class);
466 this.pingIntent.setAction("ping");
467 this.pingIntent.putExtra("time", timeToWake);
468 this.pendingPingIntent = PendingIntent.getBroadcast(context, 0,
469 this.pingIntent, 0);
470 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
471 timeToWake, pendingPingIntent);
472 } else {
473 long scheduledTime = this.pingIntent.getLongExtra("time", 0);
474 if (scheduledTime < SystemClock.elapsedRealtime()
475 || (scheduledTime > timeToWake)) {
476 this.pingIntent.putExtra("time", timeToWake);
477 alarmManager.cancel(this.pendingPingIntent);
478 this.pendingPingIntent = PendingIntent.getBroadcast(
479 context, 0, this.pingIntent, 0);
480 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
481 timeToWake, pendingPingIntent);
482 }
483 }
484 } else {
485 Intent intent = new Intent(context, EventReceiver.class);
486 intent.setAction("ping_check");
487 PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 0,
488 intent, 0);
489 alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake,
490 alarmIntent);
491 }
492
493 }
494
495 public XmppConnection createConnection(Account account) {
496 SharedPreferences sharedPref = getPreferences();
497 account.setResource(sharedPref.getString("resource", "mobile")
498 .toLowerCase(Locale.getDefault()));
499 XmppConnection connection = new XmppConnection(account, this);
500 connection.setOnMessagePacketReceivedListener(this.mMessageParser);
501 connection.setOnStatusChangedListener(this.statusListener);
502 connection.setOnPresencePacketReceivedListener(this.mPresenceParser);
503 connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser);
504 connection.setOnJinglePacketReceivedListener(this.jingleListener);
505 connection.setOnBindListener(this.mOnBindListener);
506 connection
507 .setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener);
508 return connection;
509 }
510
511 synchronized public void sendMessage(Message message) {
512 Account account = message.getConversation().getAccount();
513 Conversation conv = message.getConversation();
514 MessagePacket packet = null;
515 boolean saveInDb = true;
516 boolean send = false;
517 if (account.getStatus() == Account.STATUS_ONLINE) {
518 if (message.getType() == Message.TYPE_IMAGE) {
519 if (message.getPresence() != null) {
520 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
521 if (!conv.hasValidOtrSession()
522 && (message.getPresence() != null)) {
523 conv.startOtrSession(this, message.getPresence(),
524 true);
525 message.setStatus(Message.STATUS_WAITING);
526 } else if (conv.hasValidOtrSession()
527 && conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
528 mJingleConnectionManager
529 .createNewConnection(message);
530 } else if (message.getPresence() == null) {
531 message.setStatus(Message.STATUS_WAITING);
532 }
533 } else {
534 mJingleConnectionManager.createNewConnection(message);
535 }
536 } else {
537 message.setStatus(Message.STATUS_WAITING);
538 }
539 } else {
540 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
541 if (!conv.hasValidOtrSession()
542 && (message.getPresence() != null)) {
543 conv.startOtrSession(this, message.getPresence(), true);
544 message.setStatus(Message.STATUS_WAITING);
545 } else if (conv.hasValidOtrSession()
546 && conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
547 message.setPresence(conv.getOtrSession().getSessionID()
548 .getUserID());
549 packet = mMessageGenerator.generateOtrChat(message);
550 send = true;
551
552 } else if (message.getPresence() == null) {
553 message.setStatus(Message.STATUS_WAITING);
554 }
555 } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
556 message.getConversation().endOtrIfNeeded();
557 failWaitingOtrMessages(message.getConversation());
558 packet = mMessageGenerator.generatePgpChat(message);
559 send = true;
560 } else {
561 message.getConversation().endOtrIfNeeded();
562 failWaitingOtrMessages(message.getConversation());
563 packet = mMessageGenerator.generateChat(message);
564 send = true;
565 }
566 }
567 } else {
568 message.setStatus(Message.STATUS_WAITING);
569 if (message.getType() == Message.TYPE_TEXT) {
570 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
571 String pgpBody = message.getEncryptedBody();
572 String decryptedBody = message.getBody();
573 message.setBody(pgpBody);
574 message.setEncryption(Message.ENCRYPTION_PGP);
575 databaseBackend.createMessage(message);
576 saveInDb = false;
577 message.setBody(decryptedBody);
578 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
579 } else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
580 if (conv.hasValidOtrSession()) {
581 message.setPresence(conv.getOtrSession().getSessionID()
582 .getUserID());
583 } else if (!conv.hasValidOtrSession()
584 && message.getPresence() != null) {
585 conv.startOtrSession(this, message.getPresence(), false);
586 }
587 }
588 }
589
590 }
591 conv.getMessages().add(message);
592 if (!account.getXmppConnection().getFeatures().sm()
593 && conv.getMode() != Conversation.MODE_MULTI) {
594 message.setStatus(Message.STATUS_SEND);
595 }
596 if (saveInDb) {
597 if (message.getEncryption() == Message.ENCRYPTION_NONE
598 || saveEncryptedMessages()) {
599 databaseBackend.createMessage(message);
600 }
601 }
602 if ((send) && (packet != null)) {
603 sendMessagePacket(account, packet);
604 }
605 updateConversationUi();
606 }
607
608 private void sendUnsendMessages(Conversation conversation) {
609 for (int i = 0; i < conversation.getMessages().size(); ++i) {
610 int status = conversation.getMessages().get(i).getStatus();
611 if (status == Message.STATUS_WAITING) {
612 resendMessage(conversation.getMessages().get(i));
613 }
614 }
615 }
616
617 private void resendMessage(Message message) {
618 Account account = message.getConversation().getAccount();
619 MessagePacket packet = null;
620 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
621 Presences presences = message.getConversation().getContact()
622 .getPresences();
623 if (!message.getConversation().hasValidOtrSession()) {
624 if ((message.getPresence() != null)
625 && (presences.has(message.getPresence()))) {
626 message.getConversation().startOtrSession(this,
627 message.getPresence(), true);
628 } else {
629 if (presences.size() == 1) {
630 String presence = presences.asStringArray()[0];
631 message.getConversation().startOtrSession(this,
632 presence, true);
633 }
634 }
635 } else {
636 if (message.getConversation().getOtrSession()
637 .getSessionStatus() == SessionStatus.ENCRYPTED) {
638 if (message.getType() == Message.TYPE_TEXT) {
639 packet = mMessageGenerator.generateOtrChat(message,
640 true);
641 } else if (message.getType() == Message.TYPE_IMAGE) {
642 mJingleConnectionManager.createNewConnection(message);
643 }
644 }
645 }
646 } else if (message.getType() == Message.TYPE_TEXT) {
647 if (message.getEncryption() == Message.ENCRYPTION_NONE) {
648 packet = mMessageGenerator.generateChat(message, true);
649 } else if ((message.getEncryption() == Message.ENCRYPTION_DECRYPTED)
650 || (message.getEncryption() == Message.ENCRYPTION_PGP)) {
651 packet = mMessageGenerator.generatePgpChat(message, true);
652 }
653 } else if (message.getType() == Message.TYPE_IMAGE) {
654 Presences presences = message.getConversation().getContact()
655 .getPresences();
656 if ((message.getPresence() != null)
657 && (presences.has(message.getPresence()))) {
658 markMessage(message, Message.STATUS_OFFERED);
659 mJingleConnectionManager.createNewConnection(message);
660 } else {
661 if (presences.size() == 1) {
662 String presence = presences.asStringArray()[0];
663 message.setPresence(presence);
664 markMessage(message, Message.STATUS_OFFERED);
665 mJingleConnectionManager.createNewConnection(message);
666 }
667 }
668 }
669 if (packet != null) {
670 if (!account.getXmppConnection().getFeatures().sm()
671 && message.getConversation().getMode() != Conversation.MODE_MULTI) {
672 markMessage(message, Message.STATUS_SEND);
673 } else {
674 markMessage(message, Message.STATUS_UNSEND);
675 }
676 sendMessagePacket(account, packet);
677 }
678 }
679
680 public void fetchRosterFromServer(Account account) {
681 IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
682 if (!"".equals(account.getRosterVersion())) {
683 Log.d(Config.LOGTAG, account.getJid()
684 + ": fetching roster version " + account.getRosterVersion());
685 } else {
686 Log.d(Config.LOGTAG, account.getJid() + ": fetching roster");
687 }
688 iqPacket.query("jabber:iq:roster").setAttribute("ver",
689 account.getRosterVersion());
690 account.getXmppConnection().sendIqPacket(iqPacket,
691 new OnIqPacketReceived() {
692
693 @Override
694 public void onIqPacketReceived(final Account account,
695 IqPacket packet) {
696 Element query = packet.findChild("query");
697 if (query != null) {
698 account.getRoster().markAllAsNotInRoster();
699 mIqParser.rosterItems(account, query);
700 }
701 }
702 });
703 }
704
705 public void fetchBookmarks(Account account) {
706 IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
707 Element query = iqPacket.query("jabber:iq:private");
708 query.addChild("storage", "storage:bookmarks");
709 OnIqPacketReceived callback = new OnIqPacketReceived() {
710
711 @Override
712 public void onIqPacketReceived(Account account, IqPacket packet) {
713 Element query = packet.query();
714 List<Bookmark> bookmarks = new CopyOnWriteArrayList<Bookmark>();
715 Element storage = query.findChild("storage",
716 "storage:bookmarks");
717 if (storage != null) {
718 for (Element item : storage.getChildren()) {
719 if (item.getName().equals("conference")) {
720 Bookmark bookmark = Bookmark.parse(item, account);
721 bookmarks.add(bookmark);
722 Conversation conversation = find(bookmark);
723 if (conversation != null) {
724 conversation.setBookmark(bookmark);
725 } else {
726 if (bookmark.autojoin()) {
727 conversation = findOrCreateConversation(
728 account, bookmark.getJid(), true);
729 conversation.setBookmark(bookmark);
730 joinMuc(conversation);
731 }
732 }
733 }
734 }
735 }
736 account.setBookmarks(bookmarks);
737 }
738 };
739 sendIqPacket(account, iqPacket, callback);
740
741 }
742
743 public void pushBookmarks(Account account) {
744 IqPacket iqPacket = new IqPacket(IqPacket.TYPE_SET);
745 Element query = iqPacket.query("jabber:iq:private");
746 Element storage = query.addChild("storage", "storage:bookmarks");
747 for (Bookmark bookmark : account.getBookmarks()) {
748 storage.addChild(bookmark.toElement());
749 }
750 sendIqPacket(account, iqPacket, null);
751 }
752
753 private void mergePhoneContactsWithRoster() {
754 PhoneHelper.loadPhoneContacts(getApplicationContext(),
755 new OnPhoneContactsLoadedListener() {
756 @Override
757 public void onPhoneContactsLoaded(List<Bundle> phoneContacts) {
758 for (Account account : accounts) {
759 account.getRoster().clearSystemAccounts();
760 }
761 for (Bundle phoneContact : phoneContacts) {
762 for (Account account : accounts) {
763 String jid = phoneContact.getString("jid");
764 Contact contact = account.getRoster()
765 .getContact(jid);
766 String systemAccount = phoneContact
767 .getInt("phoneid")
768 + "#"
769 + phoneContact.getString("lookup");
770 contact.setSystemAccount(systemAccount);
771 contact.setPhotoUri(phoneContact
772 .getString("photouri"));
773 contact.setSystemName(phoneContact
774 .getString("displayname"));
775 }
776 }
777 }
778 });
779 }
780
781 public List<Conversation> getConversations() {
782 if (this.conversations == null) {
783 Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
784 for (Account account : this.accounts) {
785 accountLookupTable.put(account.getUuid(), account);
786 }
787 this.conversations = databaseBackend
788 .getConversations(Conversation.STATUS_AVAILABLE);
789 for (Conversation conv : this.conversations) {
790 Account account = accountLookupTable.get(conv.getAccountUuid());
791 conv.setAccount(account);
792 conv.setMessages(databaseBackend.getMessages(conv, 50));
793 }
794 }
795
796 return this.conversations;
797 }
798
799 public void populateWithOrderedConversations(List<Conversation> list) {
800 populateWithOrderedConversations(list, true);
801 }
802
803 public void populateWithOrderedConversations(List<Conversation> list,
804 boolean includeConferences) {
805 list.clear();
806 if (includeConferences) {
807 list.addAll(getConversations());
808 } else {
809 for (Conversation conversation : getConversations()) {
810 if (conversation.getMode() == Conversation.MODE_SINGLE) {
811 list.add(conversation);
812 }
813 }
814 }
815 Collections.sort(list, new Comparator<Conversation>() {
816 @Override
817 public int compare(Conversation lhs, Conversation rhs) {
818 Message left = lhs.getLatestMessage();
819 Message right = rhs.getLatestMessage();
820 if (left.getTimeSent() > right.getTimeSent()) {
821 return -1;
822 } else if (left.getTimeSent() < right.getTimeSent()) {
823 return 1;
824 } else {
825 return 0;
826 }
827 }
828 });
829 }
830
831 public int loadMoreMessages(Conversation conversation, long timestamp) {
832 List<Message> messages = databaseBackend.getMessages(conversation, 50,
833 timestamp);
834 for (Message message : messages) {
835 message.setConversation(conversation);
836 }
837 conversation.getMessages().addAll(0, messages);
838 return messages.size();
839 }
840
841 public List<Account> getAccounts() {
842 return this.accounts;
843 }
844
845 public Conversation find(List<Conversation> haystack, Contact contact) {
846 for (Conversation conversation : haystack) {
847 if (conversation.getContact() == contact) {
848 return conversation;
849 }
850 }
851 return null;
852 }
853
854 public Conversation find(List<Conversation> haystack, Account account,
855 String jid) {
856 for (Conversation conversation : haystack) {
857 if ((account == null || conversation.getAccount().equals(account))
858 && (conversation.getContactJid().split("/", 2)[0]
859 .equals(jid))) {
860 return conversation;
861 }
862 }
863 return null;
864 }
865
866 public Conversation findOrCreateConversation(Account account, String jid,
867 boolean muc) {
868 Conversation conversation = find(account, jid);
869 if (conversation != null) {
870 return conversation;
871 }
872 conversation = databaseBackend.findConversation(account, jid);
873 if (conversation != null) {
874 conversation.setStatus(Conversation.STATUS_AVAILABLE);
875 conversation.setAccount(account);
876 if (muc) {
877 conversation.setMode(Conversation.MODE_MULTI);
878 } else {
879 conversation.setMode(Conversation.MODE_SINGLE);
880 }
881 conversation.setMessages(databaseBackend.getMessages(conversation,
882 50));
883 this.databaseBackend.updateConversation(conversation);
884 } else {
885 String conversationName;
886 Contact contact = account.getRoster().getContact(jid);
887 if (contact != null) {
888 conversationName = contact.getDisplayName();
889 } else {
890 conversationName = jid.split("@")[0];
891 }
892 if (muc) {
893 conversation = new Conversation(conversationName, account, jid,
894 Conversation.MODE_MULTI);
895 } else {
896 conversation = new Conversation(conversationName, account, jid,
897 Conversation.MODE_SINGLE);
898 }
899 this.databaseBackend.createConversation(conversation);
900 }
901 this.conversations.add(conversation);
902 updateConversationUi();
903 return conversation;
904 }
905
906 public void archiveConversation(Conversation conversation) {
907 if (conversation.getMode() == Conversation.MODE_MULTI) {
908 Bookmark bookmark = conversation.getBookmark();
909 if (bookmark != null && bookmark.autojoin()) {
910 bookmark.setAutojoin(false);
911 pushBookmarks(bookmark.getAccount());
912 }
913 leaveMuc(conversation);
914 } else {
915 conversation.endOtrIfNeeded();
916 }
917 this.databaseBackend.updateConversation(conversation);
918 this.conversations.remove(conversation);
919 updateConversationUi();
920 }
921
922 public void clearConversationHistory(Conversation conversation) {
923 this.databaseBackend.deleteMessagesInConversation(conversation);
924 this.fileBackend.removeFiles(conversation);
925 conversation.getMessages().clear();
926 updateConversationUi();
927 }
928
929 public int getConversationCount() {
930 return this.databaseBackend.getConversationCount();
931 }
932
933 public void createAccount(Account account) {
934 databaseBackend.createAccount(account);
935 this.accounts.add(account);
936 this.reconnectAccount(account, false);
937 updateAccountUi();
938 }
939
940 public void updateAccount(Account account) {
941 this.statusListener.onStatusChanged(account);
942 databaseBackend.updateAccount(account);
943 reconnectAccount(account, false);
944 updateAccountUi();
945 UIHelper.showErrorNotification(getApplicationContext(), getAccounts());
946 }
947
948 public void deleteAccount(Account account) {
949 for (Conversation conversation : conversations) {
950 if (conversation.getAccount() == account) {
951 if (conversation.getMode() == Conversation.MODE_MULTI) {
952 leaveMuc(conversation);
953 } else if (conversation.getMode() == Conversation.MODE_SINGLE) {
954 conversation.endOtrIfNeeded();
955 }
956 conversations.remove(conversation);
957 }
958 }
959 if (account.getXmppConnection() != null) {
960 this.disconnect(account, true);
961 }
962 databaseBackend.deleteAccount(account);
963 this.accounts.remove(account);
964 updateAccountUi();
965 UIHelper.showErrorNotification(getApplicationContext(), getAccounts());
966 }
967
968 public void setOnConversationListChangedListener(
969 OnConversationUpdate listener) {
970 if (checkListeners()) {
971 switchToForeground();
972 }
973 this.mOnConversationUpdate = listener;
974 this.convChangedListenerCount++;
975 }
976
977 public void removeOnConversationListChangedListener() {
978 this.convChangedListenerCount--;
979 if (this.convChangedListenerCount == 0) {
980 this.mOnConversationUpdate = null;
981 if (checkListeners()) {
982 switchToBackground();
983 }
984 }
985 }
986
987 public void setOnAccountListChangedListener(OnAccountUpdate listener) {
988 if (checkListeners()) {
989 switchToForeground();
990 }
991 this.mOnAccountUpdate = listener;
992 this.accountChangedListenerCount++;
993 }
994
995 public void removeOnAccountListChangedListener() {
996 this.accountChangedListenerCount--;
997 if (this.accountChangedListenerCount == 0) {
998 this.mOnAccountUpdate = null;
999 if (checkListeners()) {
1000 switchToBackground();
1001 }
1002 }
1003 }
1004
1005 public void setOnRosterUpdateListener(OnRosterUpdate listener) {
1006 if (checkListeners()) {
1007 switchToForeground();
1008 }
1009 this.mOnRosterUpdate = listener;
1010 this.rosterChangedListenerCount++;
1011 }
1012
1013 public void removeOnRosterUpdateListener() {
1014 this.rosterChangedListenerCount--;
1015 if (this.rosterChangedListenerCount == 0) {
1016 this.mOnRosterUpdate = null;
1017 if (checkListeners()) {
1018 switchToBackground();
1019 }
1020 }
1021 }
1022
1023 private boolean checkListeners() {
1024 return (this.mOnAccountUpdate == null
1025 && this.mOnConversationUpdate == null && this.mOnRosterUpdate == null);
1026 }
1027
1028 private void switchToForeground() {
1029 for (Account account : getAccounts()) {
1030 if (account.getStatus() == Account.STATUS_ONLINE) {
1031 XmppConnection connection = account.getXmppConnection();
1032 if (connection != null && connection.getFeatures().csi()) {
1033 connection.sendActive();
1034 Log.d(Config.LOGTAG, account.getJid()
1035 + " sending csi//active");
1036 }
1037 }
1038 }
1039 }
1040
1041 private void switchToBackground() {
1042 for (Account account : getAccounts()) {
1043 if (account.getStatus() == Account.STATUS_ONLINE) {
1044 XmppConnection connection = account.getXmppConnection();
1045 if (connection != null && connection.getFeatures().csi()) {
1046 connection.sendInactive();
1047 Log.d(Config.LOGTAG, account.getJid()
1048 + " sending csi//inactive");
1049 }
1050 }
1051 }
1052 }
1053
1054 public void connectMultiModeConversations(Account account) {
1055 List<Conversation> conversations = getConversations();
1056 for (int i = 0; i < conversations.size(); i++) {
1057 Conversation conversation = conversations.get(i);
1058 if ((conversation.getMode() == Conversation.MODE_MULTI)
1059 && (conversation.getAccount() == account)) {
1060 joinMuc(conversation);
1061 }
1062 }
1063 }
1064
1065 public void joinMuc(Conversation conversation) {
1066 Account account = conversation.getAccount();
1067 account.pendingConferenceJoins.remove(conversation);
1068 account.pendingConferenceLeaves.remove(conversation);
1069 if (account.getStatus() == Account.STATUS_ONLINE) {
1070 Log.d(Config.LOGTAG,
1071 "joining conversation " + conversation.getContactJid());
1072 String nick = conversation.getMucOptions().getProposedNick();
1073 conversation.getMucOptions().setJoinNick(nick);
1074 PresencePacket packet = new PresencePacket();
1075 String joinJid = conversation.getMucOptions().getJoinJid();
1076 packet.setAttribute("to", conversation.getMucOptions().getJoinJid());
1077 Element x = new Element("x");
1078 x.setAttribute("xmlns", "http://jabber.org/protocol/muc");
1079 if (conversation.getMucOptions().getPassword() != null) {
1080 Element password = x.addChild("password");
1081 password.setContent(conversation.getMucOptions().getPassword());
1082 }
1083 String sig = account.getPgpSignature();
1084 if (sig != null) {
1085 packet.addChild("status").setContent("online");
1086 packet.addChild("x", "jabber:x:signed").setContent(sig);
1087 }
1088 if (conversation.getMessages().size() != 0) {
1089 final SimpleDateFormat mDateFormat = new SimpleDateFormat(
1090 "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
1091 mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
1092 Date date = new Date(conversation.getLatestMessage()
1093 .getTimeSent() + 1000);
1094 x.addChild("history").setAttribute("since",
1095 mDateFormat.format(date));
1096 }
1097 packet.addChild(x);
1098 sendPresencePacket(account, packet);
1099 if (!joinJid.equals(conversation.getContactJid())) {
1100 conversation.setContactJid(joinJid);
1101 databaseBackend.updateConversation(conversation);
1102 }
1103 } else {
1104 account.pendingConferenceJoins.add(conversation);
1105 }
1106 }
1107
1108 private OnRenameListener renameListener = null;
1109 private IqGenerator mIqGenerator = new IqGenerator(this);
1110
1111 public void setOnRenameListener(OnRenameListener listener) {
1112 this.renameListener = listener;
1113 }
1114
1115 public void providePasswordForMuc(Conversation conversation, String password) {
1116 if (conversation.getMode() == Conversation.MODE_MULTI) {
1117 conversation.getMucOptions().setPassword(password);
1118 if (conversation.getBookmark() != null
1119 && conversation.getMucOptions().isPasswordChanged()) {
1120 if (!conversation.getBookmark().autojoin()) {
1121 conversation.getBookmark().setAutojoin(true);
1122 }
1123 pushBookmarks(conversation.getAccount());
1124 }
1125 databaseBackend.updateConversation(conversation);
1126 joinMuc(conversation);
1127 }
1128 }
1129
1130 public void renameInMuc(final Conversation conversation, final String nick) {
1131 final MucOptions options = conversation.getMucOptions();
1132 options.setJoinNick(nick);
1133 if (options.online()) {
1134 Account account = conversation.getAccount();
1135 options.setOnRenameListener(new OnRenameListener() {
1136
1137 @Override
1138 public void onRename(boolean success) {
1139 if (renameListener != null) {
1140 renameListener.onRename(success);
1141 }
1142 if (success) {
1143 conversation.setContactJid(conversation.getMucOptions()
1144 .getJoinJid());
1145 databaseBackend.updateConversation(conversation);
1146 Bookmark bookmark = conversation.getBookmark();
1147 if (bookmark != null) {
1148 bookmark.setNick(nick);
1149 pushBookmarks(bookmark.getAccount());
1150 }
1151 }
1152 }
1153 });
1154 options.flagAboutToRename();
1155 PresencePacket packet = new PresencePacket();
1156 packet.setAttribute("to", options.getJoinJid());
1157 packet.setAttribute("from", conversation.getAccount().getFullJid());
1158
1159 String sig = account.getPgpSignature();
1160 if (sig != null) {
1161 packet.addChild("status").setContent("online");
1162 packet.addChild("x", "jabber:x:signed").setContent(sig);
1163 }
1164 sendPresencePacket(account, packet);
1165 } else {
1166 conversation.setContactJid(options.getJoinJid());
1167 databaseBackend.updateConversation(conversation);
1168 if (conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
1169 Bookmark bookmark = conversation.getBookmark();
1170 if (bookmark != null) {
1171 bookmark.setNick(nick);
1172 pushBookmarks(bookmark.getAccount());
1173 }
1174 joinMuc(conversation);
1175 }
1176 }
1177 }
1178
1179 public void leaveMuc(Conversation conversation) {
1180 Account account = conversation.getAccount();
1181 account.pendingConferenceJoins.remove(conversation);
1182 account.pendingConferenceLeaves.remove(conversation);
1183 if (account.getStatus() == Account.STATUS_ONLINE) {
1184 PresencePacket packet = new PresencePacket();
1185 packet.setAttribute("to", conversation.getMucOptions().getJoinJid());
1186 packet.setAttribute("from", conversation.getAccount().getFullJid());
1187 packet.setAttribute("type", "unavailable");
1188 sendPresencePacket(conversation.getAccount(), packet);
1189 conversation.getMucOptions().setOffline();
1190 conversation.deregisterWithBookmark();
1191 Log.d(Config.LOGTAG, conversation.getAccount().getJid()
1192 + " leaving muc " + conversation.getContactJid());
1193 } else {
1194 account.pendingConferenceLeaves.add(conversation);
1195 }
1196 }
1197
1198 public void disconnect(Account account, boolean force) {
1199 if ((account.getStatus() == Account.STATUS_ONLINE)
1200 || (account.getStatus() == Account.STATUS_DISABLED)) {
1201 if (!force) {
1202 List<Conversation> conversations = getConversations();
1203 for (int i = 0; i < conversations.size(); i++) {
1204 Conversation conversation = conversations.get(i);
1205 if (conversation.getAccount() == account) {
1206 if (conversation.getMode() == Conversation.MODE_MULTI) {
1207 leaveMuc(conversation);
1208 } else {
1209 conversation.endOtrIfNeeded();
1210 }
1211 }
1212 }
1213 }
1214 account.getXmppConnection().disconnect(force);
1215 }
1216 }
1217
1218 @Override
1219 public IBinder onBind(Intent intent) {
1220 return mBinder;
1221 }
1222
1223 public void updateMessage(Message message) {
1224 databaseBackend.updateMessage(message);
1225 }
1226
1227 protected void syncDirtyContacts(Account account) {
1228 for (Contact contact : account.getRoster().getContacts()) {
1229 if (contact.getOption(Contact.Options.DIRTY_PUSH)) {
1230 pushContactToServer(contact);
1231 }
1232 if (contact.getOption(Contact.Options.DIRTY_DELETE)) {
1233 deleteContactOnServer(contact);
1234 }
1235 }
1236 }
1237
1238 public void createContact(Contact contact) {
1239 SharedPreferences sharedPref = getPreferences();
1240 boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
1241 if (autoGrant) {
1242 contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
1243 contact.setOption(Contact.Options.ASKING);
1244 }
1245 pushContactToServer(contact);
1246 }
1247
1248 public void onOtrSessionEstablished(Conversation conversation) {
1249 Account account = conversation.getAccount();
1250 List<Message> messages = conversation.getMessages();
1251 Session otrSession = conversation.getOtrSession();
1252 Log.d(Config.LOGTAG,
1253 account.getJid() + " otr session established with "
1254 + conversation.getContactJid() + "/"
1255 + otrSession.getSessionID().getUserID());
1256 for (int i = 0; i < messages.size(); ++i) {
1257 Message msg = messages.get(i);
1258 if ((msg.getStatus() == Message.STATUS_UNSEND || msg.getStatus() == Message.STATUS_WAITING)
1259 && (msg.getEncryption() == Message.ENCRYPTION_OTR)) {
1260 msg.setPresence(otrSession.getSessionID().getUserID());
1261 if (msg.getType() == Message.TYPE_TEXT) {
1262 MessagePacket outPacket = mMessageGenerator
1263 .generateOtrChat(msg, true);
1264 if (outPacket != null) {
1265 msg.setStatus(Message.STATUS_SEND);
1266 databaseBackend.updateMessage(msg);
1267 sendMessagePacket(account, outPacket);
1268 }
1269 } else if (msg.getType() == Message.TYPE_IMAGE) {
1270 mJingleConnectionManager.createNewConnection(msg);
1271 }
1272 }
1273 }
1274 updateConversationUi();
1275 }
1276
1277 public boolean renewSymmetricKey(Conversation conversation) {
1278 Account account = conversation.getAccount();
1279 byte[] symmetricKey = new byte[32];
1280 this.mRandom.nextBytes(symmetricKey);
1281 Session otrSession = conversation.getOtrSession();
1282 if (otrSession != null) {
1283 MessagePacket packet = new MessagePacket();
1284 packet.setType(MessagePacket.TYPE_CHAT);
1285 packet.setFrom(account.getFullJid());
1286 packet.addChild("private", "urn:xmpp:carbons:2");
1287 packet.addChild("no-copy", "urn:xmpp:hints");
1288 packet.setTo(otrSession.getSessionID().getAccountID() + "/"
1289 + otrSession.getSessionID().getUserID());
1290 try {
1291 packet.setBody(otrSession
1292 .transformSending(CryptoHelper.FILETRANSFER
1293 + CryptoHelper.bytesToHex(symmetricKey)));
1294 sendMessagePacket(account, packet);
1295 conversation.setSymmetricKey(symmetricKey);
1296 return true;
1297 } catch (OtrException e) {
1298 return false;
1299 }
1300 }
1301 return false;
1302 }
1303
1304 public void pushContactToServer(Contact contact) {
1305 contact.resetOption(Contact.Options.DIRTY_DELETE);
1306 contact.setOption(Contact.Options.DIRTY_PUSH);
1307 Account account = contact.getAccount();
1308 if (account.getStatus() == Account.STATUS_ONLINE) {
1309 boolean ask = contact.getOption(Contact.Options.ASKING);
1310 boolean sendUpdates = contact
1311 .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)
1312 && contact.getOption(Contact.Options.PREEMPTIVE_GRANT);
1313 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
1314 iq.query("jabber:iq:roster").addChild(contact.asElement());
1315 account.getXmppConnection().sendIqPacket(iq, null);
1316 if (sendUpdates) {
1317 sendPresencePacket(account,
1318 mPresenceGenerator.sendPresenceUpdatesTo(contact));
1319 }
1320 if (ask) {
1321 sendPresencePacket(account,
1322 mPresenceGenerator.requestPresenceUpdatesFrom(contact));
1323 }
1324 }
1325 }
1326
1327 public void publishAvatar(Account account, Uri image,
1328 final UiCallback<Avatar> callback) {
1329 final Bitmap.CompressFormat format = Config.AVATAR_FORMAT;
1330 final int size = Config.AVATAR_SIZE;
1331 final Avatar avatar = getFileBackend()
1332 .getPepAvatar(image, size, format);
1333 if (avatar != null) {
1334 avatar.height = size;
1335 avatar.width = size;
1336 if (format.equals(Bitmap.CompressFormat.WEBP)) {
1337 avatar.type = "image/webp";
1338 } else if (format.equals(Bitmap.CompressFormat.JPEG)) {
1339 avatar.type = "image/jpeg";
1340 } else if (format.equals(Bitmap.CompressFormat.PNG)) {
1341 avatar.type = "image/png";
1342 }
1343 if (!getFileBackend().save(avatar)) {
1344 callback.error(R.string.error_saving_avatar, avatar);
1345 return;
1346 }
1347 IqPacket packet = this.mIqGenerator.publishAvatar(avatar);
1348 this.sendIqPacket(account, packet, new OnIqPacketReceived() {
1349
1350 @Override
1351 public void onIqPacketReceived(Account account, IqPacket result) {
1352 if (result.getType() == IqPacket.TYPE_RESULT) {
1353 IqPacket packet = XmppConnectionService.this.mIqGenerator
1354 .publishAvatarMetadata(avatar);
1355 sendIqPacket(account, packet, new OnIqPacketReceived() {
1356
1357 @Override
1358 public void onIqPacketReceived(Account account,
1359 IqPacket result) {
1360 if (result.getType() == IqPacket.TYPE_RESULT) {
1361 if (account.setAvatar(avatar.getFilename())) {
1362 databaseBackend.updateAccount(account);
1363 }
1364 callback.success(avatar);
1365 } else {
1366 callback.error(
1367 R.string.error_publish_avatar_server_reject,
1368 avatar);
1369 }
1370 }
1371 });
1372 } else {
1373 callback.error(
1374 R.string.error_publish_avatar_server_reject,
1375 avatar);
1376 }
1377 }
1378 });
1379 } else {
1380 callback.error(R.string.error_publish_avatar_converting, null);
1381 }
1382 }
1383
1384 public void fetchAvatar(Account account, Avatar avatar) {
1385 fetchAvatar(account, avatar, null);
1386 }
1387
1388 public void fetchAvatar(Account account, final Avatar avatar,
1389 final UiCallback<Avatar> callback) {
1390 IqPacket packet = this.mIqGenerator.retrieveAvatar(avatar);
1391 sendIqPacket(account, packet, new OnIqPacketReceived() {
1392
1393 @Override
1394 public void onIqPacketReceived(Account account, IqPacket result) {
1395 final String ERROR = account.getJid()
1396 + ": fetching avatar for " + avatar.owner + " failed ";
1397 if (result.getType() == IqPacket.TYPE_RESULT) {
1398 avatar.image = mIqParser.avatarData(result);
1399 if (avatar.image != null) {
1400 if (getFileBackend().save(avatar)) {
1401 if (account.getJid().equals(avatar.owner)) {
1402 if (account.setAvatar(avatar.getFilename())) {
1403 databaseBackend.updateAccount(account);
1404 }
1405 } else {
1406 Contact contact = account.getRoster()
1407 .getContact(avatar.owner);
1408 contact.setAvatar(avatar.getFilename());
1409 }
1410 if (callback != null) {
1411 callback.success(avatar);
1412 }
1413 Log.d(Config.LOGTAG, account.getJid()
1414 + ": succesfully fetched avatar for "
1415 + avatar.owner);
1416 return;
1417 }
1418 } else {
1419
1420 Log.d(Config.LOGTAG, ERROR + "(parsing error)");
1421 }
1422 } else {
1423 Element error = result.findChild("error");
1424 if (error == null) {
1425 Log.d(Config.LOGTAG, ERROR + "(server error)");
1426 } else {
1427 Log.d(Config.LOGTAG, ERROR + error.toString());
1428 }
1429 }
1430 if (callback != null) {
1431 callback.error(0, null);
1432 }
1433
1434 }
1435 });
1436 }
1437
1438 public void checkForAvatar(Account account,
1439 final UiCallback<Avatar> callback) {
1440 IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null);
1441 this.sendIqPacket(account, packet, new OnIqPacketReceived() {
1442
1443 @Override
1444 public void onIqPacketReceived(Account account, IqPacket packet) {
1445 if (packet.getType() == IqPacket.TYPE_RESULT) {
1446 Element pubsub = packet.findChild("pubsub",
1447 "http://jabber.org/protocol/pubsub");
1448 if (pubsub != null) {
1449 Element items = pubsub.findChild("items");
1450 if (items != null) {
1451 Avatar avatar = Avatar.parseMetadata(items);
1452 if (avatar != null) {
1453 avatar.owner = account.getJid();
1454 if (fileBackend.isAvatarCached(avatar)) {
1455 if (account.setAvatar(avatar.getFilename())) {
1456 databaseBackend.updateAccount(account);
1457 }
1458 callback.success(avatar);
1459 } else {
1460 fetchAvatar(account, avatar, callback);
1461 }
1462 return;
1463 }
1464 }
1465 }
1466 }
1467 callback.error(0, null);
1468 }
1469 });
1470 }
1471
1472 public void deleteContactOnServer(Contact contact) {
1473 contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
1474 contact.resetOption(Contact.Options.DIRTY_PUSH);
1475 contact.setOption(Contact.Options.DIRTY_DELETE);
1476 Account account = contact.getAccount();
1477 if (account.getStatus() == Account.STATUS_ONLINE) {
1478 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
1479 Element item = iq.query("jabber:iq:roster").addChild("item");
1480 item.setAttribute("jid", contact.getJid());
1481 item.setAttribute("subscription", "remove");
1482 account.getXmppConnection().sendIqPacket(iq, null);
1483 }
1484 }
1485
1486 public void updateConversation(Conversation conversation) {
1487 this.databaseBackend.updateConversation(conversation);
1488 }
1489
1490 public void reconnectAccount(final Account account, final boolean force) {
1491 new Thread(new Runnable() {
1492
1493 @Override
1494 public void run() {
1495 if (account.getXmppConnection() != null) {
1496 disconnect(account, force);
1497 }
1498 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
1499 if (account.getXmppConnection() == null) {
1500 account.setXmppConnection(createConnection(account));
1501 }
1502 Thread thread = new Thread(account.getXmppConnection());
1503 thread.start();
1504 scheduleWakeupCall((int) (Config.CONNECT_TIMEOUT * 1.2),
1505 false);
1506 }
1507 }
1508 }).start();
1509 }
1510
1511 public void invite(Conversation conversation, String contact) {
1512 MessagePacket packet = mMessageGenerator.invite(conversation, contact);
1513 sendMessagePacket(conversation.getAccount(), packet);
1514 }
1515
1516 public void resetSendingToWaiting(Account account) {
1517 for (Conversation conversation : getConversations()) {
1518 if (conversation.getAccount() == account) {
1519 for (Message message : conversation.getMessages()) {
1520 if (message.getType() != Message.TYPE_IMAGE
1521 && message.getStatus() == Message.STATUS_UNSEND) {
1522 markMessage(message, Message.STATUS_WAITING);
1523 }
1524 }
1525 }
1526 }
1527 }
1528
1529 public boolean markMessage(Account account, String recipient, String uuid,
1530 int status) {
1531 for (Conversation conversation : getConversations()) {
1532 if (conversation.getContactJid().equals(recipient)
1533 && conversation.getAccount().equals(account)) {
1534 return markMessage(conversation, uuid, status);
1535 }
1536 }
1537 return false;
1538 }
1539
1540 public boolean markMessage(Conversation conversation, String uuid,
1541 int status) {
1542 for (Message message : conversation.getMessages()) {
1543 if (message.getUuid().equals(uuid)) {
1544 markMessage(message, status);
1545 return true;
1546 }
1547 }
1548 return false;
1549 }
1550
1551 public void markMessage(Message message, int status) {
1552 if (status == Message.STATUS_SEND_FAILED
1553 && (message.getStatus() == Message.STATUS_SEND_RECEIVED || message
1554 .getStatus() == Message.STATUS_SEND_DISPLAYED)) {
1555 return;
1556 }
1557 message.setStatus(status);
1558 databaseBackend.updateMessage(message);
1559 updateConversationUi();
1560 }
1561
1562 public SharedPreferences getPreferences() {
1563 return PreferenceManager
1564 .getDefaultSharedPreferences(getApplicationContext());
1565 }
1566
1567 public boolean forceEncryption() {
1568 return getPreferences().getBoolean("force_encryption", false);
1569 }
1570
1571 public boolean confirmMessages() {
1572 return getPreferences().getBoolean("confirm_messages", true);
1573 }
1574
1575 public boolean saveEncryptedMessages() {
1576 return !getPreferences().getBoolean("dont_save_encrypted", false);
1577 }
1578
1579 public boolean indicateReceived() {
1580 return getPreferences().getBoolean("indicate_received", false);
1581 }
1582
1583 public void updateConversationUi() {
1584 if (mOnConversationUpdate != null) {
1585 mOnConversationUpdate.onConversationUpdate();
1586 }
1587 }
1588
1589 public void updateAccountUi() {
1590 if (mOnAccountUpdate != null) {
1591 mOnAccountUpdate.onAccountUpdate();
1592 }
1593 }
1594
1595 public void updateRosterUi() {
1596 if (mOnRosterUpdate != null) {
1597 mOnRosterUpdate.onRosterUpdate();
1598 }
1599 }
1600
1601 public Account findAccountByJid(String accountJid) {
1602 for (Account account : this.accounts) {
1603 if (account.getJid().equals(accountJid)) {
1604 return account;
1605 }
1606 }
1607 return null;
1608 }
1609
1610 public Conversation findConversationByUuid(String uuid) {
1611 for (Conversation conversation : getConversations()) {
1612 if (conversation.getUuid().equals(uuid)) {
1613 return conversation;
1614 }
1615 }
1616 return null;
1617 }
1618
1619 public void markRead(Conversation conversation) {
1620 conversation.markRead();
1621 mNotificationService.clear(conversation);
1622 String id = conversation.popLatestMarkableMessageId();
1623 if (confirmMessages() && id != null) {
1624 Account account = conversation.getAccount();
1625 String to = conversation.getContactJid();
1626 this.sendMessagePacket(conversation.getAccount(),
1627 mMessageGenerator.confirm(account, to, id));
1628 }
1629 }
1630
1631 public void failWaitingOtrMessages(Conversation conversation) {
1632 for (Message message : conversation.getMessages()) {
1633 if (message.getEncryption() == Message.ENCRYPTION_OTR
1634 && message.getStatus() == Message.STATUS_WAITING) {
1635 markMessage(message, Message.STATUS_SEND_FAILED);
1636 }
1637 }
1638 }
1639
1640 public SecureRandom getRNG() {
1641 return this.mRandom;
1642 }
1643
1644 public MemorizingTrustManager getMemorizingTrustManager() {
1645 return this.mMemorizingTrustManager;
1646 }
1647
1648 public PowerManager getPowerManager() {
1649 return this.pm;
1650 }
1651
1652 public void replyWithNotAcceptable(Account account, MessagePacket packet) {
1653 if (account.getStatus() == Account.STATUS_ONLINE) {
1654 MessagePacket error = this.mMessageGenerator
1655 .generateNotAcceptable(packet);
1656 sendMessagePacket(account, error);
1657 }
1658 }
1659
1660 public void syncRosterToDisk(final Account account) {
1661 new Thread(new Runnable() {
1662
1663 @Override
1664 public void run() {
1665 databaseBackend.writeRoster(account.getRoster());
1666 }
1667 }).start();
1668
1669 }
1670
1671 public List<String> getKnownHosts() {
1672 List<String> hosts = new ArrayList<String>();
1673 for (Account account : getAccounts()) {
1674 if (!hosts.contains(account.getServer())) {
1675 hosts.add(account.getServer());
1676 }
1677 for (Contact contact : account.getRoster().getContacts()) {
1678 if (contact.showInRoster()) {
1679 String server = contact.getServer();
1680 if (server != null && !hosts.contains(server)) {
1681 hosts.add(server);
1682 }
1683 }
1684 }
1685 }
1686 return hosts;
1687 }
1688
1689 public List<String> getKnownConferenceHosts() {
1690 ArrayList<String> mucServers = new ArrayList<String>();
1691 for (Account account : accounts) {
1692 if (account.getXmppConnection() != null) {
1693 String server = account.getXmppConnection().getMucServer();
1694 if (server != null && !mucServers.contains(server)) {
1695 mucServers.add(server);
1696 }
1697 }
1698 }
1699 return mucServers;
1700 }
1701
1702 public void sendMessagePacket(Account account, MessagePacket packet) {
1703 account.getXmppConnection().sendMessagePacket(packet);
1704 }
1705
1706 public void sendPresencePacket(Account account, PresencePacket packet) {
1707 account.getXmppConnection().sendPresencePacket(packet);
1708 }
1709
1710 public void sendIqPacket(Account account, IqPacket packet,
1711 OnIqPacketReceived callback) {
1712 account.getXmppConnection().sendIqPacket(packet, callback);
1713 }
1714
1715 public MessageGenerator getMessageGenerator() {
1716 return this.mMessageGenerator;
1717 }
1718
1719 public PresenceGenerator getPresenceGenerator() {
1720 return this.mPresenceGenerator;
1721 }
1722
1723 public IqGenerator getIqGenerator() {
1724 return this.mIqGenerator;
1725 }
1726
1727 public JingleConnectionManager getJingleConnectionManager() {
1728 return this.mJingleConnectionManager;
1729 }
1730
1731 public interface OnConversationUpdate {
1732 public void onConversationUpdate();
1733 }
1734
1735 public interface OnAccountUpdate {
1736 public void onAccountUpdate();
1737 }
1738
1739 public interface OnRosterUpdate {
1740 public void onRosterUpdate();
1741 }
1742
1743 public List<Contact> findContacts(String jid) {
1744 ArrayList<Contact> contacts = new ArrayList<Contact>();
1745 for (Account account : getAccounts()) {
1746 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
1747 Contact contact = account.getRoster()
1748 .getContactAsShownInRoster(jid);
1749 if (contact != null) {
1750 contacts.add(contact);
1751 }
1752 }
1753 }
1754 return contacts;
1755 }
1756
1757 public void pushNotification(Message message) {
1758 this.mNotificationService.push(message);
1759 }
1760}