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