1package eu.siacs.conversations.entities;
2
3import android.content.ContentValues;
4import android.database.Cursor;
5
6import java.net.MalformedURLException;
7import java.net.URL;
8
9import eu.siacs.conversations.Config;
10import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
11import eu.siacs.conversations.utils.CryptoHelper;
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 = "\n\u200B\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 similar(Message message) {
381 if (type != TYPE_PRIVATE && 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 && CryptoHelper.UUID_PATTERN.matcher(message.getRemoteMsgId()).matches()));
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 this.getBody().length() + message.getBody().length() <= Config.MAX_DISPLAY_MESSAGE_CHARS &&
468 !GeoHelper.isGeoUri(message.getBody()) &&
469 !GeoHelper.isGeoUri(this.body) &&
470 message.treatAsDownloadable() == Decision.NEVER &&
471 this.treatAsDownloadable() == Decision.NEVER &&
472 !message.getBody().startsWith(ME_COMMAND) &&
473 !this.getBody().startsWith(ME_COMMAND) &&
474 !this.bodyIsHeart() &&
475 !message.bodyIsHeart() &&
476 this.isTrusted() == message.isTrusted()
477 );
478 }
479
480 private static boolean isStatusMergeable(int a, int b) {
481 return a == b || (
482 (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_UNSEND)
483 || (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_SEND)
484 || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND)
485 || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND_RECEIVED)
486 || (a == Message.STATUS_SEND && b == Message.STATUS_UNSEND)
487 || (a == Message.STATUS_SEND && b == Message.STATUS_SEND_RECEIVED)
488 );
489 }
490
491 public String getMergedBody() {
492 StringBuilder body = new StringBuilder(this.body.trim());
493 Message current = this;
494 while(current.mergeable(current.next())) {
495 current = current.next();
496 if (current == null) {
497 break;
498 }
499 body.append(MERGE_SEPARATOR);
500 body.append(current.getBody().trim());
501 }
502 return body.toString();
503 }
504
505 public boolean hasMeCommand() {
506 return getMergedBody().startsWith(ME_COMMAND);
507 }
508
509 public int getMergedStatus() {
510 int status = this.status;
511 Message current = this;
512 while(current.mergeable(current.next())) {
513 current = current.next();
514 if (current == null) {
515 break;
516 }
517 status = current.status;
518 }
519 return status;
520 }
521
522 public long getMergedTimeSent() {
523 long time = this.timeSent;
524 Message current = this;
525 while(current.mergeable(current.next())) {
526 current = current.next();
527 if (current == null) {
528 break;
529 }
530 time = current.timeSent;
531 }
532 return time;
533 }
534
535 public boolean wasMergedIntoPrevious() {
536 Message prev = this.prev();
537 return prev != null && prev.mergeable(this);
538 }
539
540 public boolean trusted() {
541 Contact contact = this.getContact();
542 return (status > STATUS_RECEIVED || (contact != null && contact.trusted()));
543 }
544
545 public boolean fixCounterpart() {
546 Presences presences = conversation.getContact().getPresences();
547 if (counterpart != null && presences.has(counterpart.getResourcepart())) {
548 return true;
549 } else if (presences.size() >= 1) {
550 try {
551 counterpart = Jid.fromParts(conversation.getJid().getLocalpart(),
552 conversation.getJid().getDomainpart(),
553 presences.toResourceArray()[0]);
554 return true;
555 } catch (InvalidJidException e) {
556 counterpart = null;
557 return false;
558 }
559 } else {
560 counterpart = null;
561 return false;
562 }
563 }
564
565 public void setUuid(String uuid) {
566 this.uuid = uuid;
567 }
568
569 public String getEditedId() {
570 return edited;
571 }
572
573 public void setOob(boolean isOob) {
574 this.oob = isOob;
575 }
576
577 public enum Decision {
578 MUST,
579 SHOULD,
580 NEVER,
581 }
582
583 private static String extractRelevantExtension(URL url) {
584 String path = url.getPath();
585 return extractRelevantExtension(path);
586 }
587
588 private static String extractRelevantExtension(String path) {
589 if (path == null || path.isEmpty()) {
590 return null;
591 }
592
593 String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase();
594 int dotPosition = filename.lastIndexOf(".");
595
596 if (dotPosition != -1) {
597 String extension = filename.substring(dotPosition + 1);
598 // we want the real file extension, not the crypto one
599 if (Transferable.VALID_CRYPTO_EXTENSIONS.contains(extension)) {
600 return extractRelevantExtension(filename.substring(0,dotPosition));
601 } else {
602 return extension;
603 }
604 }
605 return null;
606 }
607
608 public String getMimeType() {
609 if (relativeFilePath != null) {
610 int start = relativeFilePath.lastIndexOf('.') + 1;
611 if (start < relativeFilePath.length()) {
612 return MimeUtils.guessMimeTypeFromExtension(relativeFilePath.substring(start));
613 } else {
614 return null;
615 }
616 } else {
617 try {
618 return MimeUtils.guessMimeTypeFromExtension(extractRelevantExtension(new URL(body.trim())));
619 } catch (MalformedURLException e) {
620 return null;
621 }
622 }
623 }
624
625 public Decision treatAsDownloadable() {
626 if (body.trim().contains(" ")) {
627 return Decision.NEVER;
628 }
629 try {
630 URL url = new URL(body);
631 if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
632 return Decision.NEVER;
633 } else if (oob) {
634 return Decision.MUST;
635 }
636 String extension = extractRelevantExtension(url);
637 if (extension == null) {
638 return Decision.NEVER;
639 }
640 String ref = url.getRef();
641 boolean encrypted = ref != null && ref.matches("([A-Fa-f0-9]{2}){48}");
642
643 if (encrypted) {
644 return Decision.MUST;
645 } else if (Transferable.VALID_IMAGE_EXTENSIONS.contains(extension)
646 || Transferable.WELL_KNOWN_EXTENSIONS.contains(extension)) {
647 return Decision.SHOULD;
648 } else {
649 return Decision.NEVER;
650 }
651
652 } catch (MalformedURLException e) {
653 return Decision.NEVER;
654 }
655 }
656
657 public boolean bodyIsHeart() {
658 return body != null && UIHelper.HEARTS.contains(body.trim());
659 }
660
661 public FileParams getFileParams() {
662 FileParams params = getLegacyFileParams();
663 if (params != null) {
664 return params;
665 }
666 params = new FileParams();
667 if (this.transferable != null) {
668 params.size = this.transferable.getFileSize();
669 }
670 if (body == null) {
671 return params;
672 }
673 String parts[] = body.split("\\|");
674 switch (parts.length) {
675 case 1:
676 try {
677 params.size = Long.parseLong(parts[0]);
678 } catch (NumberFormatException e) {
679 try {
680 params.url = new URL(parts[0]);
681 } catch (MalformedURLException e1) {
682 params.url = null;
683 }
684 }
685 break;
686 case 2:
687 case 4:
688 try {
689 params.url = new URL(parts[0]);
690 } catch (MalformedURLException e1) {
691 params.url = null;
692 }
693 try {
694 params.size = Long.parseLong(parts[1]);
695 } catch (NumberFormatException e) {
696 params.size = 0;
697 }
698 try {
699 params.width = Integer.parseInt(parts[2]);
700 } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
701 params.width = 0;
702 }
703 try {
704 params.height = Integer.parseInt(parts[3]);
705 } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
706 params.height = 0;
707 }
708 break;
709 case 3:
710 try {
711 params.size = Long.parseLong(parts[0]);
712 } catch (NumberFormatException e) {
713 params.size = 0;
714 }
715 try {
716 params.width = Integer.parseInt(parts[1]);
717 } catch (NumberFormatException e) {
718 params.width = 0;
719 }
720 try {
721 params.height = Integer.parseInt(parts[2]);
722 } catch (NumberFormatException e) {
723 params.height = 0;
724 }
725 break;
726 }
727 return params;
728 }
729
730 public FileParams getLegacyFileParams() {
731 FileParams params = new FileParams();
732 if (body == null) {
733 return params;
734 }
735 String parts[] = body.split(",");
736 if (parts.length == 3) {
737 try {
738 params.size = Long.parseLong(parts[0]);
739 } catch (NumberFormatException e) {
740 return null;
741 }
742 try {
743 params.width = Integer.parseInt(parts[1]);
744 } catch (NumberFormatException e) {
745 return null;
746 }
747 try {
748 params.height = Integer.parseInt(parts[2]);
749 } catch (NumberFormatException e) {
750 return null;
751 }
752 return params;
753 } else {
754 return null;
755 }
756 }
757
758 public void untie() {
759 this.mNextMessage = null;
760 this.mPreviousMessage = null;
761 }
762
763 public boolean isFileOrImage() {
764 return type == TYPE_FILE || type == TYPE_IMAGE;
765 }
766
767 public boolean hasFileOnRemoteHost() {
768 return isFileOrImage() && getFileParams().url != null;
769 }
770
771 public boolean needsUploading() {
772 return isFileOrImage() && getFileParams().url == null;
773 }
774
775 public class FileParams {
776 public URL url;
777 public long size = 0;
778 public int width = 0;
779 public int height = 0;
780 }
781
782 public void setFingerprint(String fingerprint) {
783 this.axolotlFingerprint = fingerprint;
784 }
785
786 public String getFingerprint() {
787 return axolotlFingerprint;
788 }
789
790 public boolean isTrusted() {
791 XmppAxolotlSession.Trust t = conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint);
792 return t != null && t.trusted();
793 }
794
795 private int getPreviousEncryption() {
796 for (Message iterator = this.prev(); iterator != null; iterator = iterator.prev()){
797 if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) {
798 continue;
799 }
800 return iterator.getEncryption();
801 }
802 return ENCRYPTION_NONE;
803 }
804
805 private int getNextEncryption() {
806 for (Message iterator = this.next(); iterator != null; iterator = iterator.next()){
807 if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) {
808 continue;
809 }
810 return iterator.getEncryption();
811 }
812 return conversation.getNextEncryption();
813 }
814
815 public boolean isValidInSession() {
816 int pastEncryption = getCleanedEncryption(this.getPreviousEncryption());
817 int futureEncryption = getCleanedEncryption(this.getNextEncryption());
818
819 boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE
820 || futureEncryption == ENCRYPTION_NONE
821 || pastEncryption != futureEncryption;
822
823 return inUnencryptedSession || getCleanedEncryption(this.getEncryption()) == pastEncryption;
824 }
825
826 private static int getCleanedEncryption(int encryption) {
827 if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) {
828 return ENCRYPTION_PGP;
829 }
830 return encryption;
831 }
832}