1package eu.siacs.conversations.entities;
2
3import android.content.ContentValues;
4import android.database.Cursor;
5
6import java.net.MalformedURLException;
7import java.net.URL;
8import java.util.Arrays;
9
10import eu.siacs.conversations.Config;
11import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
12import eu.siacs.conversations.utils.GeoHelper;
13import eu.siacs.conversations.utils.MimeUtils;
14import eu.siacs.conversations.utils.UIHelper;
15import eu.siacs.conversations.xmpp.jid.InvalidJidException;
16import eu.siacs.conversations.xmpp.jid.Jid;
17
18public class Message extends AbstractEntity {
19
20 public static final String TABLENAME = "messages";
21
22 public static final String MERGE_SEPARATOR = " \u200B\n\n";
23
24 public static final int STATUS_RECEIVED = 0;
25 public static final int STATUS_UNSEND = 1;
26 public static final int STATUS_SEND = 2;
27 public static final int STATUS_SEND_FAILED = 3;
28 public static final int STATUS_WAITING = 5;
29 public static final int STATUS_OFFERED = 6;
30 public static final int STATUS_SEND_RECEIVED = 7;
31 public static final int STATUS_SEND_DISPLAYED = 8;
32
33 public static final int ENCRYPTION_NONE = 0;
34 public static final int ENCRYPTION_PGP = 1;
35 public static final int ENCRYPTION_OTR = 2;
36 public static final int ENCRYPTION_DECRYPTED = 3;
37 public static final int ENCRYPTION_DECRYPTION_FAILED = 4;
38 public static final int ENCRYPTION_AXOLOTL = 5;
39
40 public static final int TYPE_TEXT = 0;
41 public static final int TYPE_IMAGE = 1;
42 public static final int TYPE_FILE = 2;
43 public static final int TYPE_STATUS = 3;
44 public static final int TYPE_PRIVATE = 4;
45
46 public static final String CONVERSATION = "conversationUuid";
47 public static final String COUNTERPART = "counterpart";
48 public static final String TRUE_COUNTERPART = "trueCounterpart";
49 public static final String BODY = "body";
50 public static final String TIME_SENT = "timeSent";
51 public static final String ENCRYPTION = "encryption";
52 public static final String STATUS = "status";
53 public static final String TYPE = "type";
54 public static final String CARBON = "carbon";
55 public static final String OOB = "oob";
56 public static final String EDITED = "edited";
57 public static final String REMOTE_MSG_ID = "remoteMsgId";
58 public static final String SERVER_MSG_ID = "serverMsgId";
59 public static final String RELATIVE_FILE_PATH = "relativeFilePath";
60 public static final String FINGERPRINT = "axolotl_fingerprint";
61 public static final String READ = "read";
62 public static final String ME_COMMAND = "/me ";
63
64
65 public boolean markable = false;
66 protected String conversationUuid;
67 protected Jid counterpart;
68 protected Jid trueCounterpart;
69 protected String body;
70 protected String encryptedBody;
71 protected long timeSent;
72 protected int encryption;
73 protected int status;
74 protected int type;
75 protected boolean carbon = false;
76 protected boolean oob = false;
77 protected String edited = null;
78 protected String relativeFilePath;
79 protected boolean read = true;
80 protected String remoteMsgId = null;
81 protected String serverMsgId = null;
82 protected Conversation conversation = null;
83 protected Transferable transferable = null;
84 private Message mNextMessage = null;
85 private Message mPreviousMessage = null;
86 private String axolotlFingerprint = null;
87
88 private Message() {
89
90 }
91
92 public Message(Conversation conversation, String body, int encryption) {
93 this(conversation, body, encryption, STATUS_UNSEND);
94 }
95
96 public Message(Conversation conversation, String body, int encryption, int status) {
97 this(java.util.UUID.randomUUID().toString(),
98 conversation.getUuid(),
99 conversation.getJid() == null ? null : conversation.getJid().toBareJid(),
100 null,
101 body,
102 System.currentTimeMillis(),
103 encryption,
104 status,
105 TYPE_TEXT,
106 false,
107 null,
108 null,
109 null,
110 null,
111 true,
112 null,
113 false);
114 this.conversation = conversation;
115 }
116
117 private Message(final String uuid, final String conversationUUid, final Jid counterpart,
118 final Jid trueCounterpart, final String body, final long timeSent,
119 final int encryption, final int status, final int type, final boolean carbon,
120 final String remoteMsgId, final String relativeFilePath,
121 final String serverMsgId, final String fingerprint, final boolean read,
122 final String edited, final boolean oob) {
123 this.uuid = uuid;
124 this.conversationUuid = conversationUUid;
125 this.counterpart = counterpart;
126 this.trueCounterpart = trueCounterpart;
127 this.body = body;
128 this.timeSent = timeSent;
129 this.encryption = encryption;
130 this.status = status;
131 this.type = type;
132 this.carbon = carbon;
133 this.remoteMsgId = remoteMsgId;
134 this.relativeFilePath = relativeFilePath;
135 this.serverMsgId = serverMsgId;
136 this.axolotlFingerprint = fingerprint;
137 this.read = read;
138 this.edited = edited;
139 this.oob = oob;
140 }
141
142 public static Message fromCursor(Cursor cursor) {
143 Jid jid;
144 try {
145 String value = cursor.getString(cursor.getColumnIndex(COUNTERPART));
146 if (value != null) {
147 jid = Jid.fromString(value, true);
148 } else {
149 jid = null;
150 }
151 } catch (InvalidJidException e) {
152 jid = null;
153 }
154 Jid trueCounterpart;
155 try {
156 String value = cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART));
157 if (value != null) {
158 trueCounterpart = Jid.fromString(value, true);
159 } else {
160 trueCounterpart = null;
161 }
162 } catch (InvalidJidException e) {
163 trueCounterpart = null;
164 }
165 return new Message(cursor.getString(cursor.getColumnIndex(UUID)),
166 cursor.getString(cursor.getColumnIndex(CONVERSATION)),
167 jid,
168 trueCounterpart,
169 cursor.getString(cursor.getColumnIndex(BODY)),
170 cursor.getLong(cursor.getColumnIndex(TIME_SENT)),
171 cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
172 cursor.getInt(cursor.getColumnIndex(STATUS)),
173 cursor.getInt(cursor.getColumnIndex(TYPE)),
174 cursor.getInt(cursor.getColumnIndex(CARBON)) > 0,
175 cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)),
176 cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)),
177 cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)),
178 cursor.getString(cursor.getColumnIndex(FINGERPRINT)),
179 cursor.getInt(cursor.getColumnIndex(READ)) > 0,
180 cursor.getString(cursor.getColumnIndex(EDITED)),
181 cursor.getInt(cursor.getColumnIndex(OOB)) > 0);
182 }
183
184 public static Message createStatusMessage(Conversation conversation, String body) {
185 final Message message = new Message();
186 message.setType(Message.TYPE_STATUS);
187 message.setConversation(conversation);
188 message.setBody(body);
189 return message;
190 }
191
192 public static Message createLoadMoreMessage(Conversation conversation) {
193 final Message message = new Message();
194 message.setType(Message.TYPE_STATUS);
195 message.setConversation(conversation);
196 message.setBody("LOAD_MORE");
197 return message;
198 }
199
200 @Override
201 public ContentValues getContentValues() {
202 ContentValues values = new ContentValues();
203 values.put(UUID, uuid);
204 values.put(CONVERSATION, conversationUuid);
205 if (counterpart == null) {
206 values.putNull(COUNTERPART);
207 } else {
208 values.put(COUNTERPART, counterpart.toString());
209 }
210 if (trueCounterpart == null) {
211 values.putNull(TRUE_COUNTERPART);
212 } else {
213 values.put(TRUE_COUNTERPART, trueCounterpart.toString());
214 }
215 values.put(BODY, body);
216 values.put(TIME_SENT, timeSent);
217 values.put(ENCRYPTION, encryption);
218 values.put(STATUS, status);
219 values.put(TYPE, type);
220 values.put(CARBON, carbon ? 1 : 0);
221 values.put(REMOTE_MSG_ID, remoteMsgId);
222 values.put(RELATIVE_FILE_PATH, relativeFilePath);
223 values.put(SERVER_MSG_ID, serverMsgId);
224 values.put(FINGERPRINT, axolotlFingerprint);
225 values.put(READ,read ? 1 : 0);
226 values.put(EDITED, edited);
227 values.put(OOB, oob ? 1 : 0);
228 return values;
229 }
230
231 public String getConversationUuid() {
232 return conversationUuid;
233 }
234
235 public Conversation getConversation() {
236 return this.conversation;
237 }
238
239 public void setConversation(Conversation conv) {
240 this.conversation = conv;
241 }
242
243 public Jid getCounterpart() {
244 return counterpart;
245 }
246
247 public void setCounterpart(final Jid counterpart) {
248 this.counterpart = counterpart;
249 }
250
251 public Contact getContact() {
252 if (this.conversation.getMode() == Conversation.MODE_SINGLE) {
253 return this.conversation.getContact();
254 } else {
255 if (this.trueCounterpart == null) {
256 return null;
257 } else {
258 return this.conversation.getAccount().getRoster()
259 .getContactFromRoster(this.trueCounterpart);
260 }
261 }
262 }
263
264 public String getBody() {
265 return body;
266 }
267
268 public void setBody(String body) {
269 this.body = body;
270 }
271
272 public long getTimeSent() {
273 return timeSent;
274 }
275
276 public int getEncryption() {
277 return encryption;
278 }
279
280 public void setEncryption(int encryption) {
281 this.encryption = encryption;
282 }
283
284 public int getStatus() {
285 return status;
286 }
287
288 public void setStatus(int status) {
289 this.status = status;
290 }
291
292 public String getRelativeFilePath() {
293 return this.relativeFilePath;
294 }
295
296 public void setRelativeFilePath(String path) {
297 this.relativeFilePath = path;
298 }
299
300 public String getRemoteMsgId() {
301 return this.remoteMsgId;
302 }
303
304 public void setRemoteMsgId(String id) {
305 this.remoteMsgId = id;
306 }
307
308 public String getServerMsgId() {
309 return this.serverMsgId;
310 }
311
312 public void setServerMsgId(String id) {
313 this.serverMsgId = id;
314 }
315
316 public boolean isRead() {
317 return this.read;
318 }
319
320 public void markRead() {
321 this.read = true;
322 }
323
324 public void markUnread() {
325 this.read = false;
326 }
327
328 public void setTime(long time) {
329 this.timeSent = time;
330 }
331
332 public String getEncryptedBody() {
333 return this.encryptedBody;
334 }
335
336 public void setEncryptedBody(String body) {
337 this.encryptedBody = body;
338 }
339
340 public int getType() {
341 return this.type;
342 }
343
344 public void setType(int type) {
345 this.type = type;
346 }
347
348 public boolean isCarbon() {
349 return carbon;
350 }
351
352 public void setCarbon(boolean carbon) {
353 this.carbon = carbon;
354 }
355
356 public void setEdited(String edited) {
357 this.edited = edited;
358 }
359
360 public boolean edited() {
361 return this.edited != null;
362 }
363
364 public void setTrueCounterpart(Jid trueCounterpart) {
365 this.trueCounterpart = trueCounterpart;
366 }
367
368 public Jid getTrueCounterpart() {
369 return this.trueCounterpart;
370 }
371
372 public Transferable getTransferable() {
373 return this.transferable;
374 }
375
376 public void setTransferable(Transferable transferable) {
377 this.transferable = transferable;
378 }
379
380 public boolean equals(Message message) {
381 if (this.serverMsgId != null && message.getServerMsgId() != null) {
382 return this.serverMsgId.equals(message.getServerMsgId());
383 } else if (this.body == null || this.counterpart == null) {
384 return false;
385 } else {
386 String body, otherBody;
387 if (this.hasFileOnRemoteHost()) {
388 body = getFileParams().url.toString();
389 otherBody = message.body == null ? null : message.body.trim();
390 } else {
391 body = this.body;
392 otherBody = message.body;
393 }
394 if (message.getRemoteMsgId() != null) {
395 return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid))
396 && this.counterpart.equals(message.getCounterpart())
397 && (body.equals(otherBody)
398 ||(message.getEncryption() == Message.ENCRYPTION_PGP
399 && message.getRemoteMsgId().matches("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"))) ;
400 } else {
401 return this.remoteMsgId == null
402 && this.counterpart.equals(message.getCounterpart())
403 && body.equals(otherBody)
404 && Math.abs(this.getTimeSent() - message.getTimeSent()) < Config.MESSAGE_MERGE_WINDOW * 1000;
405 }
406 }
407 }
408
409 public Message next() {
410 synchronized (this.conversation.messages) {
411 if (this.mNextMessage == null) {
412 int index = this.conversation.messages.indexOf(this);
413 if (index < 0 || index >= this.conversation.messages.size() - 1) {
414 this.mNextMessage = null;
415 } else {
416 this.mNextMessage = this.conversation.messages.get(index + 1);
417 }
418 }
419 return this.mNextMessage;
420 }
421 }
422
423 public Message prev() {
424 synchronized (this.conversation.messages) {
425 if (this.mPreviousMessage == null) {
426 int index = this.conversation.messages.indexOf(this);
427 if (index <= 0 || index > this.conversation.messages.size()) {
428 this.mPreviousMessage = null;
429 } else {
430 this.mPreviousMessage = this.conversation.messages.get(index - 1);
431 }
432 }
433 return this.mPreviousMessage;
434 }
435 }
436
437 public boolean isLastCorrectableMessage() {
438 Message next = next();
439 while(next != null) {
440 if (next.isCorrectable()) {
441 return false;
442 }
443 next = next.next();
444 }
445 return isCorrectable();
446 }
447
448 private boolean isCorrectable() {
449 return getStatus() != STATUS_RECEIVED && !isCarbon();
450 }
451
452 public boolean mergeable(final Message message) {
453 return message != null &&
454 (message.getType() == Message.TYPE_TEXT &&
455 this.getTransferable() == null &&
456 message.getTransferable() == null &&
457 message.getEncryption() != Message.ENCRYPTION_PGP &&
458 message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED &&
459 this.getType() == message.getType() &&
460 //this.getStatus() == message.getStatus() &&
461 isStatusMergeable(this.getStatus(), message.getStatus()) &&
462 this.getEncryption() == message.getEncryption() &&
463 this.getCounterpart() != null &&
464 this.getCounterpart().equals(message.getCounterpart()) &&
465 this.edited() == message.edited() &&
466 (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) &&
467 !GeoHelper.isGeoUri(message.getBody()) &&
468 !GeoHelper.isGeoUri(this.body) &&
469 message.treatAsDownloadable() == Decision.NEVER &&
470 this.treatAsDownloadable() == Decision.NEVER &&
471 !message.getBody().startsWith(ME_COMMAND) &&
472 !this.getBody().startsWith(ME_COMMAND) &&
473 !this.bodyIsHeart() &&
474 !message.bodyIsHeart() &&
475 this.isTrusted() == message.isTrusted()
476 );
477 }
478
479 private static boolean isStatusMergeable(int a, int b) {
480 return a == b || (
481 (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_UNSEND)
482 || (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_SEND)
483 || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND)
484 || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND_RECEIVED)
485 || (a == Message.STATUS_SEND && b == Message.STATUS_UNSEND)
486 || (a == Message.STATUS_SEND && b == Message.STATUS_SEND_RECEIVED)
487 );
488 }
489
490 public String getMergedBody() {
491 StringBuilder body = new StringBuilder(this.body.trim());
492 Message current = this;
493 while(current.mergeable(current.next())) {
494 current = current.next();
495 body.append(MERGE_SEPARATOR);
496 body.append(current.getBody().trim());
497 }
498 return body.toString();
499 }
500
501 public boolean hasMeCommand() {
502 return getMergedBody().startsWith(ME_COMMAND);
503 }
504
505 public int getMergedStatus() {
506 int status = this.status;
507 Message current = this;
508 while(current.mergeable(current.next())) {
509 current = current.next();
510 status = current.status;
511 }
512 return status;
513 }
514
515 public long getMergedTimeSent() {
516 long time = this.timeSent;
517 Message current = this;
518 while(current.mergeable(current.next())) {
519 current = current.next();
520 time = current.timeSent;
521 }
522 return time;
523 }
524
525 public boolean wasMergedIntoPrevious() {
526 Message prev = this.prev();
527 return prev != null && prev.mergeable(this);
528 }
529
530 public boolean trusted() {
531 Contact contact = this.getContact();
532 return (status > STATUS_RECEIVED || (contact != null && contact.trusted()));
533 }
534
535 public boolean fixCounterpart() {
536 Presences presences = conversation.getContact().getPresences();
537 if (counterpart != null && presences.has(counterpart.getResourcepart())) {
538 return true;
539 } else if (presences.size() >= 1) {
540 try {
541 counterpart = Jid.fromParts(conversation.getJid().getLocalpart(),
542 conversation.getJid().getDomainpart(),
543 presences.asStringArray()[0]);
544 return true;
545 } catch (InvalidJidException e) {
546 counterpart = null;
547 return false;
548 }
549 } else {
550 counterpart = null;
551 return false;
552 }
553 }
554
555 public void setUuid(String uuid) {
556 this.uuid = uuid;
557 }
558
559 public String getEditedId() {
560 return edited;
561 }
562
563 public void setOob(boolean isOob) {
564 this.oob = isOob;
565 }
566
567 public enum Decision {
568 MUST,
569 SHOULD,
570 NEVER,
571 }
572
573 private static String extractRelevantExtension(URL url) {
574 String path = url.getPath();
575 return extractRelevantExtension(path);
576 }
577
578 private static String extractRelevantExtension(String path) {
579 if (path == null || path.isEmpty()) {
580 return null;
581 }
582
583 String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase();
584 int dotPosition = filename.lastIndexOf(".");
585
586 if (dotPosition != -1) {
587 String extension = filename.substring(dotPosition + 1);
588 // we want the real file extension, not the crypto one
589 if (Transferable.VALID_CRYPTO_EXTENSIONS.contains(extension)) {
590 return extractRelevantExtension(filename.substring(0,dotPosition));
591 } else {
592 return extension;
593 }
594 }
595 return null;
596 }
597
598 public String getMimeType() {
599 if (relativeFilePath != null) {
600 int start = relativeFilePath.lastIndexOf('.') + 1;
601 if (start < relativeFilePath.length()) {
602 return MimeUtils.guessMimeTypeFromExtension(relativeFilePath.substring(start));
603 } else {
604 return null;
605 }
606 } else {
607 try {
608 return MimeUtils.guessMimeTypeFromExtension(extractRelevantExtension(new URL(body.trim())));
609 } catch (MalformedURLException e) {
610 return null;
611 }
612 }
613 }
614
615 public Decision treatAsDownloadable() {
616 if (body.trim().contains(" ")) {
617 return Decision.NEVER;
618 }
619 try {
620 URL url = new URL(body);
621 if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
622 return Decision.NEVER;
623 } else if (oob) {
624 return Decision.MUST;
625 }
626 String extension = extractRelevantExtension(url);
627 if (extension == null) {
628 return Decision.NEVER;
629 }
630 String ref = url.getRef();
631 boolean encrypted = ref != null && ref.matches("([A-Fa-f0-9]{2}){48}");
632
633 if (encrypted) {
634 return Decision.MUST;
635 } else if (Transferable.VALID_IMAGE_EXTENSIONS.contains(extension)
636 || Transferable.WELL_KNOWN_EXTENSIONS.contains(extension)) {
637 return Decision.SHOULD;
638 } else {
639 return Decision.NEVER;
640 }
641
642 } catch (MalformedURLException e) {
643 return Decision.NEVER;
644 }
645 }
646
647 public boolean bodyIsHeart() {
648 return body != null && UIHelper.HEARTS.contains(body.trim());
649 }
650
651 public FileParams getFileParams() {
652 FileParams params = getLegacyFileParams();
653 if (params != null) {
654 return params;
655 }
656 params = new FileParams();
657 if (this.transferable != null) {
658 params.size = this.transferable.getFileSize();
659 }
660 if (body == null) {
661 return params;
662 }
663 String parts[] = body.split("\\|");
664 switch (parts.length) {
665 case 1:
666 try {
667 params.size = Long.parseLong(parts[0]);
668 } catch (NumberFormatException e) {
669 try {
670 params.url = new URL(parts[0]);
671 } catch (MalformedURLException e1) {
672 params.url = null;
673 }
674 }
675 break;
676 case 2:
677 case 4:
678 try {
679 params.url = new URL(parts[0]);
680 } catch (MalformedURLException e1) {
681 params.url = null;
682 }
683 try {
684 params.size = Long.parseLong(parts[1]);
685 } catch (NumberFormatException e) {
686 params.size = 0;
687 }
688 try {
689 params.width = Integer.parseInt(parts[2]);
690 } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
691 params.width = 0;
692 }
693 try {
694 params.height = Integer.parseInt(parts[3]);
695 } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
696 params.height = 0;
697 }
698 break;
699 case 3:
700 try {
701 params.size = Long.parseLong(parts[0]);
702 } catch (NumberFormatException e) {
703 params.size = 0;
704 }
705 try {
706 params.width = Integer.parseInt(parts[1]);
707 } catch (NumberFormatException e) {
708 params.width = 0;
709 }
710 try {
711 params.height = Integer.parseInt(parts[2]);
712 } catch (NumberFormatException e) {
713 params.height = 0;
714 }
715 break;
716 }
717 return params;
718 }
719
720 public FileParams getLegacyFileParams() {
721 FileParams params = new FileParams();
722 if (body == null) {
723 return params;
724 }
725 String parts[] = body.split(",");
726 if (parts.length == 3) {
727 try {
728 params.size = Long.parseLong(parts[0]);
729 } catch (NumberFormatException e) {
730 return null;
731 }
732 try {
733 params.width = Integer.parseInt(parts[1]);
734 } catch (NumberFormatException e) {
735 return null;
736 }
737 try {
738 params.height = Integer.parseInt(parts[2]);
739 } catch (NumberFormatException e) {
740 return null;
741 }
742 return params;
743 } else {
744 return null;
745 }
746 }
747
748 public void untie() {
749 this.mNextMessage = null;
750 this.mPreviousMessage = null;
751 }
752
753 public boolean isFileOrImage() {
754 return type == TYPE_FILE || type == TYPE_IMAGE;
755 }
756
757 public boolean hasFileOnRemoteHost() {
758 return isFileOrImage() && getFileParams().url != null;
759 }
760
761 public boolean needsUploading() {
762 return isFileOrImage() && getFileParams().url == null;
763 }
764
765 public class FileParams {
766 public URL url;
767 public long size = 0;
768 public int width = 0;
769 public int height = 0;
770 }
771
772 public void setFingerprint(String fingerprint) {
773 this.axolotlFingerprint = fingerprint;
774 }
775
776 public String getFingerprint() {
777 return axolotlFingerprint;
778 }
779
780 public boolean isTrusted() {
781 XmppAxolotlSession.Trust t = conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint);
782 return t != null && t.trusted();
783 }
784
785 private int getPreviousEncryption() {
786 for (Message iterator = this.prev(); iterator != null; iterator = iterator.prev()){
787 if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) {
788 continue;
789 }
790 return iterator.getEncryption();
791 }
792 return ENCRYPTION_NONE;
793 }
794
795 private int getNextEncryption() {
796 for (Message iterator = this.next(); iterator != null; iterator = iterator.next()){
797 if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) {
798 continue;
799 }
800 return iterator.getEncryption();
801 }
802 return conversation.getNextEncryption();
803 }
804
805 public boolean isValidInSession() {
806 int pastEncryption = getCleanedEncryption(this.getPreviousEncryption());
807 int futureEncryption = getCleanedEncryption(this.getNextEncryption());
808
809 boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE
810 || futureEncryption == ENCRYPTION_NONE
811 || pastEncryption != futureEncryption;
812
813 return inUnencryptedSession || getCleanedEncryption(this.getEncryption()) == pastEncryption;
814 }
815
816 private static int getCleanedEncryption(int encryption) {
817 if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) {
818 return ENCRYPTION_PGP;
819 }
820 return encryption;
821 }
822}