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