1package eu.siacs.conversations.entities;
2
3import android.content.ContentValues;
4import android.database.Cursor;
5
6import net.java.otr4j.OtrException;
7import net.java.otr4j.crypto.OtrCryptoException;
8import net.java.otr4j.session.SessionID;
9import net.java.otr4j.session.SessionImpl;
10import net.java.otr4j.session.SessionStatus;
11
12import org.json.JSONException;
13import org.json.JSONObject;
14
15import java.security.interfaces.DSAPublicKey;
16import java.util.ArrayList;
17import java.util.Collections;
18import java.util.Comparator;
19import java.util.Iterator;
20import java.util.List;
21
22import eu.siacs.conversations.Config;
23import eu.siacs.conversations.crypto.axolotl.AxolotlService;
24import eu.siacs.conversations.xmpp.chatstate.ChatState;
25import eu.siacs.conversations.xmpp.jid.InvalidJidException;
26import eu.siacs.conversations.xmpp.jid.Jid;
27
28public class Conversation extends AbstractEntity implements Blockable {
29 public static final String TABLENAME = "conversations";
30
31 public static final int STATUS_AVAILABLE = 0;
32 public static final int STATUS_ARCHIVED = 1;
33 public static final int STATUS_DELETED = 2;
34
35 public static final int MODE_MULTI = 1;
36 public static final int MODE_SINGLE = 0;
37
38 public static final String NAME = "name";
39 public static final String ACCOUNT = "accountUuid";
40 public static final String CONTACT = "contactUuid";
41 public static final String CONTACTJID = "contactJid";
42 public static final String STATUS = "status";
43 public static final String CREATED = "created";
44 public static final String MODE = "mode";
45 public static final String ATTRIBUTES = "attributes";
46
47 public static final String ATTRIBUTE_NEXT_ENCRYPTION = "next_encryption";
48 public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password";
49 public static final String ATTRIBUTE_MUTED_TILL = "muted_till";
50 public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify";
51
52 private String name;
53 private String contactUuid;
54 private String accountUuid;
55 private Jid contactJid;
56 private int status;
57 private long created;
58 private int mode;
59
60 private JSONObject attributes = new JSONObject();
61
62 private Jid nextCounterpart;
63
64 protected final ArrayList<Message> messages = new ArrayList<>();
65 protected Account account = null;
66
67 private transient SessionImpl otrSession;
68
69 private transient String otrFingerprint = null;
70 private Smp mSmp = new Smp();
71
72 private String nextMessage;
73
74 private transient MucOptions mucOptions = null;
75
76 private byte[] symmetricKey;
77
78 private Bookmark bookmark;
79
80 private boolean messagesLeftOnServer = true;
81 private ChatState mOutgoingChatState = Config.DEFAULT_CHATSTATE;
82 private ChatState mIncomingChatState = Config.DEFAULT_CHATSTATE;
83 private String mLastReceivedOtrMessageId = null;
84 private String mFirstMamReference = null;
85 private Message correctingMessage;
86
87 public boolean hasMessagesLeftOnServer() {
88 return messagesLeftOnServer;
89 }
90
91 public void setHasMessagesLeftOnServer(boolean value) {
92 this.messagesLeftOnServer = value;
93 }
94
95 public Message findUnsentMessageWithUuid(String uuid) {
96 synchronized(this.messages) {
97 for (final Message message : this.messages) {
98 final int s = message.getStatus();
99 if ((s == Message.STATUS_UNSEND || s == Message.STATUS_WAITING) && message.getUuid().equals(uuid)) {
100 return message;
101 }
102 }
103 }
104 return null;
105 }
106
107 public void findWaitingMessages(OnMessageFound onMessageFound) {
108 synchronized (this.messages) {
109 for(Message message : this.messages) {
110 if (message.getStatus() == Message.STATUS_WAITING) {
111 onMessageFound.onMessageFound(message);
112 }
113 }
114 }
115 }
116
117 public void findUnreadMessages(OnMessageFound onMessageFound) {
118 synchronized (this.messages) {
119 for(Message message : this.messages) {
120 if (!message.isRead()) {
121 onMessageFound.onMessageFound(message);
122 }
123 }
124 }
125 }
126
127 public void findMessagesWithFiles(final OnMessageFound onMessageFound) {
128 synchronized (this.messages) {
129 for (final Message message : this.messages) {
130 if ((message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE)
131 && message.getEncryption() != Message.ENCRYPTION_PGP) {
132 onMessageFound.onMessageFound(message);
133 }
134 }
135 }
136 }
137
138 public Message findMessageWithFileAndUuid(final String uuid) {
139 synchronized (this.messages) {
140 for (final Message message : this.messages) {
141 if ((message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE)
142 && message.getEncryption() != Message.ENCRYPTION_PGP
143 && message.getUuid().equals(uuid)) {
144 return message;
145 }
146 }
147 }
148 return null;
149 }
150
151 public void clearMessages() {
152 synchronized (this.messages) {
153 this.messages.clear();
154 }
155 }
156
157 public boolean setIncomingChatState(ChatState state) {
158 if (this.mIncomingChatState == state) {
159 return false;
160 }
161 this.mIncomingChatState = state;
162 return true;
163 }
164
165 public ChatState getIncomingChatState() {
166 return this.mIncomingChatState;
167 }
168
169 public boolean setOutgoingChatState(ChatState state) {
170 if (mode == MODE_MULTI) {
171 return false;
172 }
173 if (this.mOutgoingChatState != state) {
174 this.mOutgoingChatState = state;
175 return true;
176 } else {
177 return false;
178 }
179 }
180
181 public ChatState getOutgoingChatState() {
182 return this.mOutgoingChatState;
183 }
184
185 public void trim() {
186 synchronized (this.messages) {
187 final int size = messages.size();
188 final int maxsize = Config.PAGE_SIZE * Config.MAX_NUM_PAGES;
189 if (size > maxsize) {
190 this.messages.subList(0, size - maxsize).clear();
191 }
192 }
193 }
194
195 public void findUnsentMessagesWithEncryption(int encryptionType, OnMessageFound onMessageFound) {
196 synchronized (this.messages) {
197 for (Message message : this.messages) {
198 if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING)
199 && (message.getEncryption() == encryptionType)) {
200 onMessageFound.onMessageFound(message);
201 }
202 }
203 }
204 }
205
206 public void findUnsentTextMessages(OnMessageFound onMessageFound) {
207 synchronized (this.messages) {
208 for (Message message : this.messages) {
209 if (message.getType() != Message.TYPE_IMAGE
210 && message.getStatus() == Message.STATUS_UNSEND) {
211 onMessageFound.onMessageFound(message);
212 }
213 }
214 }
215 }
216
217 public Message findSentMessageWithUuidOrRemoteId(String id) {
218 synchronized (this.messages) {
219 for (Message message : this.messages) {
220 if (id.equals(message.getUuid())
221 || (message.getStatus() >= Message.STATUS_SEND
222 && id.equals(message.getRemoteMsgId()))) {
223 return message;
224 }
225 }
226 }
227 return null;
228 }
229
230 public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart, boolean received, boolean carbon) {
231 synchronized (this.messages) {
232 for(int i = this.messages.size() - 1; i >= 0; --i) {
233 Message message = messages.get(i);
234 if (counterpart.equals(message.getCounterpart())
235 && ((message.getStatus() == Message.STATUS_RECEIVED) == received)
236 && (carbon == message.isCarbon() || received) ) {
237 if (id.equals(message.getRemoteMsgId())) {
238 return message;
239 } else {
240 return null;
241 }
242 }
243 }
244 }
245 return null;
246 }
247
248 public Message findSentMessageWithUuid(String id) {
249 synchronized (this.messages) {
250 for (Message message : this.messages) {
251 if (id.equals(message.getUuid())) {
252 return message;
253 }
254 }
255 }
256 return null;
257 }
258
259 public void populateWithMessages(final List<Message> messages) {
260 synchronized (this.messages) {
261 messages.clear();
262 messages.addAll(this.messages);
263 }
264 for(Iterator<Message> iterator = messages.iterator(); iterator.hasNext();) {
265 if (iterator.next().wasMergedIntoPrevious()) {
266 iterator.remove();
267 }
268 }
269 }
270
271 @Override
272 public boolean isBlocked() {
273 return getContact().isBlocked();
274 }
275
276 @Override
277 public boolean isDomainBlocked() {
278 return getContact().isDomainBlocked();
279 }
280
281 @Override
282 public Jid getBlockedJid() {
283 return getContact().getBlockedJid();
284 }
285
286 public String getLastReceivedOtrMessageId() {
287 return this.mLastReceivedOtrMessageId;
288 }
289
290 public void setLastReceivedOtrMessageId(String id) {
291 this.mLastReceivedOtrMessageId = id;
292 }
293
294 public int countMessages() {
295 synchronized (this.messages) {
296 return this.messages.size();
297 }
298 }
299
300 public void setFirstMamReference(String reference) {
301 this.mFirstMamReference = reference;
302 }
303
304 public String getFirstMamReference() {
305 return this.mFirstMamReference;
306 }
307
308 public void setLastClearHistory(long time) {
309 setAttribute("last_clear_history",String.valueOf(time));
310 }
311
312 public long getLastClearHistory() {
313 return getLongAttribute("last_clear_history", 0);
314 }
315
316 public void setCorrectingMessage(Message correctingMessage) {
317 this.correctingMessage = correctingMessage;
318 }
319
320 public Message getCorrectingMessage() {
321 return this.correctingMessage;
322 }
323
324 public interface OnMessageFound {
325 void onMessageFound(final Message message);
326 }
327
328 public Conversation(final String name, final Account account, final Jid contactJid,
329 final int mode) {
330 this(java.util.UUID.randomUUID().toString(), name, null, account
331 .getUuid(), contactJid, System.currentTimeMillis(),
332 STATUS_AVAILABLE, mode, "");
333 this.account = account;
334 }
335
336 public Conversation(final String uuid, final String name, final String contactUuid,
337 final String accountUuid, final Jid contactJid, final long created, final int status,
338 final int mode, final String attributes) {
339 this.uuid = uuid;
340 this.name = name;
341 this.contactUuid = contactUuid;
342 this.accountUuid = accountUuid;
343 this.contactJid = contactJid;
344 this.created = created;
345 this.status = status;
346 this.mode = mode;
347 try {
348 this.attributes = new JSONObject(attributes == null ? "" : attributes);
349 } catch (JSONException e) {
350 this.attributes = new JSONObject();
351 }
352 }
353
354 public boolean isRead() {
355 return (this.messages.size() == 0) || this.messages.get(this.messages.size() - 1).isRead();
356 }
357
358 public List<Message> markRead() {
359 final List<Message> unread = new ArrayList<>();
360 synchronized (this.messages) {
361 for(Message message : this.messages) {
362 if (!message.isRead()) {
363 message.markRead();
364 unread.add(message);
365 }
366 }
367 }
368 return unread;
369 }
370
371 public Message getLatestMarkableMessage() {
372 for (int i = this.messages.size() - 1; i >= 0; --i) {
373 if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED
374 && this.messages.get(i).markable) {
375 if (this.messages.get(i).isRead()) {
376 return null;
377 } else {
378 return this.messages.get(i);
379 }
380 }
381 }
382 return null;
383 }
384
385 public Message getLatestMessage() {
386 if (this.messages.size() == 0) {
387 Message message = new Message(this, "", Message.ENCRYPTION_NONE);
388 message.setTime(getCreated());
389 return message;
390 } else {
391 Message message = this.messages.get(this.messages.size() - 1);
392 message.setConversation(this);
393 return message;
394 }
395 }
396
397 public String getName() {
398 if (getMode() == MODE_MULTI) {
399 if (getMucOptions().getSubject() != null) {
400 return getMucOptions().getSubject();
401 } else if (bookmark != null && bookmark.getBookmarkName() != null) {
402 return bookmark.getBookmarkName();
403 } else {
404 String generatedName = getMucOptions().createNameFromParticipants();
405 if (generatedName != null) {
406 return generatedName;
407 } else {
408 return getJid().getLocalpart();
409 }
410 }
411 } else {
412 return this.getContact().getDisplayName();
413 }
414 }
415
416 public String getAccountUuid() {
417 return this.accountUuid;
418 }
419
420 public Account getAccount() {
421 return this.account;
422 }
423
424 public Contact getContact() {
425 return this.account.getRoster().getContact(this.contactJid);
426 }
427
428 public void setAccount(final Account account) {
429 this.account = account;
430 }
431
432 @Override
433 public Jid getJid() {
434 return this.contactJid;
435 }
436
437 public int getStatus() {
438 return this.status;
439 }
440
441 public long getCreated() {
442 return this.created;
443 }
444
445 public ContentValues getContentValues() {
446 ContentValues values = new ContentValues();
447 values.put(UUID, uuid);
448 values.put(NAME, name);
449 values.put(CONTACT, contactUuid);
450 values.put(ACCOUNT, accountUuid);
451 values.put(CONTACTJID, contactJid.toString());
452 values.put(CREATED, created);
453 values.put(STATUS, status);
454 values.put(MODE, mode);
455 values.put(ATTRIBUTES, attributes.toString());
456 return values;
457 }
458
459 public static Conversation fromCursor(Cursor cursor) {
460 Jid jid;
461 try {
462 jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(CONTACTJID)), true);
463 } catch (final InvalidJidException e) {
464 // Borked DB..
465 jid = null;
466 }
467 return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)),
468 cursor.getString(cursor.getColumnIndex(NAME)),
469 cursor.getString(cursor.getColumnIndex(CONTACT)),
470 cursor.getString(cursor.getColumnIndex(ACCOUNT)),
471 jid,
472 cursor.getLong(cursor.getColumnIndex(CREATED)),
473 cursor.getInt(cursor.getColumnIndex(STATUS)),
474 cursor.getInt(cursor.getColumnIndex(MODE)),
475 cursor.getString(cursor.getColumnIndex(ATTRIBUTES)));
476 }
477
478 public void setStatus(int status) {
479 this.status = status;
480 }
481
482 public int getMode() {
483 return this.mode;
484 }
485
486 public void setMode(int mode) {
487 this.mode = mode;
488 }
489
490 public SessionImpl startOtrSession(String presence, boolean sendStart) {
491 if (this.otrSession != null) {
492 return this.otrSession;
493 } else {
494 final SessionID sessionId = new SessionID(this.getJid().toBareJid().toString(),
495 presence,
496 "xmpp");
497 this.otrSession = new SessionImpl(sessionId, getAccount().getOtrService());
498 try {
499 if (sendStart) {
500 this.otrSession.startSession();
501 return this.otrSession;
502 }
503 return this.otrSession;
504 } catch (OtrException e) {
505 return null;
506 }
507 }
508
509 }
510
511 public SessionImpl getOtrSession() {
512 return this.otrSession;
513 }
514
515 public void resetOtrSession() {
516 this.otrFingerprint = null;
517 this.otrSession = null;
518 this.mSmp.hint = null;
519 this.mSmp.secret = null;
520 this.mSmp.status = Smp.STATUS_NONE;
521 }
522
523 public Smp smp() {
524 return mSmp;
525 }
526
527 public boolean startOtrIfNeeded() {
528 if (this.otrSession != null && this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) {
529 try {
530 this.otrSession.startSession();
531 return true;
532 } catch (OtrException e) {
533 this.resetOtrSession();
534 return false;
535 }
536 } else {
537 return true;
538 }
539 }
540
541 public boolean endOtrIfNeeded() {
542 if (this.otrSession != null) {
543 if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
544 try {
545 this.otrSession.endSession();
546 this.resetOtrSession();
547 return true;
548 } catch (OtrException e) {
549 this.resetOtrSession();
550 return false;
551 }
552 } else {
553 this.resetOtrSession();
554 return false;
555 }
556 } else {
557 return false;
558 }
559 }
560
561 public boolean hasValidOtrSession() {
562 return this.otrSession != null;
563 }
564
565 public synchronized String getOtrFingerprint() {
566 if (this.otrFingerprint == null) {
567 try {
568 if (getOtrSession() == null || getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) {
569 return null;
570 }
571 DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession().getRemotePublicKey();
572 this.otrFingerprint = getAccount().getOtrService().getFingerprint(remotePubKey);
573 } catch (final OtrCryptoException | UnsupportedOperationException ignored) {
574 return null;
575 }
576 }
577 return this.otrFingerprint;
578 }
579
580 public boolean verifyOtrFingerprint() {
581 final String fingerprint = getOtrFingerprint();
582 if (fingerprint != null) {
583 getContact().addOtrFingerprint(fingerprint);
584 return true;
585 } else {
586 return false;
587 }
588 }
589
590 public boolean isOtrFingerprintVerified() {
591 return getContact().getOtrFingerprints().contains(getOtrFingerprint());
592 }
593
594 /**
595 * short for is Private and Non-anonymous
596 */
597 private boolean isPnNA() {
598 return mode == MODE_SINGLE || (getMucOptions().membersOnly() && getMucOptions().nonanonymous());
599 }
600
601 public synchronized MucOptions getMucOptions() {
602 if (this.mucOptions == null) {
603 this.mucOptions = new MucOptions(this);
604 }
605 return this.mucOptions;
606 }
607
608 public void resetMucOptions() {
609 this.mucOptions = null;
610 }
611
612 public void setContactJid(final Jid jid) {
613 this.contactJid = jid;
614 }
615
616 public void setNextCounterpart(Jid jid) {
617 this.nextCounterpart = jid;
618 }
619
620 public Jid getNextCounterpart() {
621 return this.nextCounterpart;
622 }
623
624 private int getMostRecentlyUsedOutgoingEncryption() {
625 synchronized (this.messages) {
626 for(int i = this.messages.size() -1; i >= 0; --i) {
627 final Message m = this.messages.get(i);
628 if (!m.isCarbon() && m.getStatus() != Message.STATUS_RECEIVED) {
629 final int e = m.getEncryption();
630 if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) {
631 return Message.ENCRYPTION_PGP;
632 } else {
633 return e;
634 }
635 }
636 }
637 }
638 return Message.ENCRYPTION_NONE;
639 }
640
641 private int getMostRecentlyUsedIncomingEncryption() {
642 synchronized (this.messages) {
643 for(int i = this.messages.size() -1; i >= 0; --i) {
644 final Message m = this.messages.get(i);
645 if (m.getStatus() == Message.STATUS_RECEIVED) {
646 final int e = m.getEncryption();
647 if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) {
648 return Message.ENCRYPTION_PGP;
649 } else {
650 return e;
651 }
652 }
653 }
654 }
655 return Message.ENCRYPTION_NONE;
656 }
657
658 public int getNextEncryption() {
659 final AxolotlService axolotlService = getAccount().getAxolotlService();
660 int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
661 if (next == -1) {
662 if (Config.X509_VERIFICATION) {
663 if (axolotlService != null && axolotlService.isConversationAxolotlCapable(this)) {
664 return Message.ENCRYPTION_AXOLOTL;
665 } else {
666 return Message.ENCRYPTION_NONE;
667 }
668 }
669 int outgoing = this.getMostRecentlyUsedOutgoingEncryption();
670 if (outgoing == Message.ENCRYPTION_NONE) {
671 next = this.getMostRecentlyUsedIncomingEncryption();
672 } else {
673 next = outgoing;
674 }
675 }
676
677 if (!Config.supportUnencrypted() && next <= 0) {
678 if (Config.supportOmemo()
679 && (axolotlService != null && axolotlService.isConversationAxolotlCapable(this) || !Config.multipleEncryptionChoices())) {
680 return Message.ENCRYPTION_AXOLOTL;
681 } else if (Config.supportOtr() && mode == MODE_SINGLE) {
682 return Message.ENCRYPTION_OTR;
683 } else if (Config.supportOpenPgp()
684 && (mode == MODE_SINGLE) || !Config.multipleEncryptionChoices()) {
685 return Message.ENCRYPTION_PGP;
686 }
687 } else if (next == Message.ENCRYPTION_AXOLOTL
688 && (!Config.supportOmemo() || axolotlService == null || !axolotlService.isConversationAxolotlCapable(this))) {
689 next = Message.ENCRYPTION_NONE;
690 }
691 return next;
692 }
693
694 public void setNextEncryption(int encryption) {
695 this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, String.valueOf(encryption));
696 }
697
698 public String getNextMessage() {
699 if (this.nextMessage == null) {
700 return "";
701 } else {
702 return this.nextMessage;
703 }
704 }
705
706 public boolean smpRequested() {
707 return smp().status == Smp.STATUS_CONTACT_REQUESTED;
708 }
709
710 public void setNextMessage(String message) {
711 this.nextMessage = message;
712 }
713
714 public void setSymmetricKey(byte[] key) {
715 this.symmetricKey = key;
716 }
717
718 public byte[] getSymmetricKey() {
719 return this.symmetricKey;
720 }
721
722 public void setBookmark(Bookmark bookmark) {
723 this.bookmark = bookmark;
724 this.bookmark.setConversation(this);
725 }
726
727 public void deregisterWithBookmark() {
728 if (this.bookmark != null) {
729 this.bookmark.setConversation(null);
730 }
731 }
732
733 public Bookmark getBookmark() {
734 return this.bookmark;
735 }
736
737 public boolean hasDuplicateMessage(Message message) {
738 synchronized (this.messages) {
739 for (int i = this.messages.size() - 1; i >= 0; --i) {
740 if (this.messages.get(i).equals(message)) {
741 return true;
742 }
743 }
744 }
745 return false;
746 }
747
748 public Message findSentMessageWithBody(String body) {
749 synchronized (this.messages) {
750 for (int i = this.messages.size() - 1; i >= 0; --i) {
751 Message message = this.messages.get(i);
752 if (message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) {
753 String otherBody;
754 if (message.hasFileOnRemoteHost()) {
755 otherBody = message.getFileParams().url.toString();
756 } else {
757 otherBody = message.body;
758 }
759 if (otherBody != null && otherBody.equals(body)) {
760 return message;
761 }
762 }
763 }
764 return null;
765 }
766 }
767
768 public long getLastMessageTransmitted() {
769 long last_clear = getLastClearHistory();
770 if (last_clear != 0) {
771 return last_clear;
772 }
773 synchronized (this.messages) {
774 for(int i = this.messages.size() - 1; i >= 0; --i) {
775 Message message = this.messages.get(i);
776 if (message.getStatus() == Message.STATUS_RECEIVED || message.isCarbon()) {
777 return message.getTimeSent();
778 }
779 }
780 }
781 return 0;
782 }
783
784 public void setMutedTill(long value) {
785 this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value));
786 }
787
788 public boolean isMuted() {
789 return System.currentTimeMillis() < this.getLongAttribute(ATTRIBUTE_MUTED_TILL, 0);
790 }
791
792 public boolean alwaysNotify() {
793 return mode == MODE_SINGLE || getBooleanAttribute(ATTRIBUTE_ALWAYS_NOTIFY, Config.ALWAYS_NOTIFY_BY_DEFAULT || isPnNA());
794 }
795
796 public boolean setAttribute(String key, String value) {
797 try {
798 this.attributes.put(key, value);
799 return true;
800 } catch (JSONException e) {
801 return false;
802 }
803 }
804
805 public String getAttribute(String key) {
806 try {
807 return this.attributes.getString(key);
808 } catch (JSONException e) {
809 return null;
810 }
811 }
812
813 public int getIntAttribute(String key, int defaultValue) {
814 String value = this.getAttribute(key);
815 if (value == null) {
816 return defaultValue;
817 } else {
818 try {
819 return Integer.parseInt(value);
820 } catch (NumberFormatException e) {
821 return defaultValue;
822 }
823 }
824 }
825
826 public long getLongAttribute(String key, long defaultValue) {
827 String value = this.getAttribute(key);
828 if (value == null) {
829 return defaultValue;
830 } else {
831 try {
832 return Long.parseLong(value);
833 } catch (NumberFormatException e) {
834 return defaultValue;
835 }
836 }
837 }
838
839 public boolean getBooleanAttribute(String key, boolean defaultValue) {
840 String value = this.getAttribute(key);
841 if (value == null) {
842 return defaultValue;
843 } else {
844 return Boolean.parseBoolean(value);
845 }
846 }
847
848 public void add(Message message) {
849 message.setConversation(this);
850 synchronized (this.messages) {
851 this.messages.add(message);
852 }
853 }
854
855 public void prepend(Message message) {
856 message.setConversation(this);
857 synchronized (this.messages) {
858 this.messages.add(0,message);
859 }
860 }
861
862 public void addAll(int index, List<Message> messages) {
863 synchronized (this.messages) {
864 this.messages.addAll(index, messages);
865 }
866 account.getPgpDecryptionService().addAll(messages);
867 }
868
869 public void sort() {
870 synchronized (this.messages) {
871 Collections.sort(this.messages, new Comparator<Message>() {
872 @Override
873 public int compare(Message left, Message right) {
874 if (left.getTimeSent() < right.getTimeSent()) {
875 return -1;
876 } else if (left.getTimeSent() > right.getTimeSent()) {
877 return 1;
878 } else {
879 return 0;
880 }
881 }
882 });
883 for(Message message : this.messages) {
884 message.untie();
885 }
886 }
887 }
888
889 public int unreadCount() {
890 synchronized (this.messages) {
891 int count = 0;
892 for(int i = this.messages.size() - 1; i >= 0; --i) {
893 if (this.messages.get(i).isRead()) {
894 return count;
895 }
896 ++count;
897 }
898 return count;
899 }
900 }
901
902 public class Smp {
903 public static final int STATUS_NONE = 0;
904 public static final int STATUS_CONTACT_REQUESTED = 1;
905 public static final int STATUS_WE_REQUESTED = 2;
906 public static final int STATUS_FAILED = 3;
907 public static final int STATUS_VERIFIED = 4;
908
909 public String secret = null;
910 public String hint = null;
911 public int status = 0;
912 }
913}