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