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