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 == null ? "" : 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.toPreppedString());
209 }
210 if (trueCounterpart == null) {
211 values.putNull(TRUE_COUNTERPART);
212 } else {
213 values.put(TRUE_COUNTERPART, trueCounterpart.toPreppedString());
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 if (body == null) {
270 throw new Error("You should not set the message body to null");
271 }
272 this.body = body;
273 }
274
275 public long getTimeSent() {
276 return timeSent;
277 }
278
279 public int getEncryption() {
280 return encryption;
281 }
282
283 public void setEncryption(int encryption) {
284 this.encryption = encryption;
285 }
286
287 public int getStatus() {
288 return status;
289 }
290
291 public void setStatus(int status) {
292 this.status = status;
293 }
294
295 public String getRelativeFilePath() {
296 return this.relativeFilePath;
297 }
298
299 public void setRelativeFilePath(String path) {
300 this.relativeFilePath = path;
301 }
302
303 public String getRemoteMsgId() {
304 return this.remoteMsgId;
305 }
306
307 public void setRemoteMsgId(String id) {
308 this.remoteMsgId = id;
309 }
310
311 public String getServerMsgId() {
312 return this.serverMsgId;
313 }
314
315 public void setServerMsgId(String id) {
316 this.serverMsgId = id;
317 }
318
319 public boolean isRead() {
320 return this.read;
321 }
322
323 public void markRead() {
324 this.read = true;
325 }
326
327 public void markUnread() {
328 this.read = false;
329 }
330
331 public void setTime(long time) {
332 this.timeSent = time;
333 }
334
335 public String getEncryptedBody() {
336 return this.encryptedBody;
337 }
338
339 public void setEncryptedBody(String body) {
340 this.encryptedBody = body;
341 }
342
343 public int getType() {
344 return this.type;
345 }
346
347 public void setType(int type) {
348 this.type = type;
349 }
350
351 public boolean isCarbon() {
352 return carbon;
353 }
354
355 public void setCarbon(boolean carbon) {
356 this.carbon = carbon;
357 }
358
359 public void setEdited(String edited) {
360 this.edited = edited;
361 }
362
363 public boolean edited() {
364 return this.edited != null;
365 }
366
367 public void setTrueCounterpart(Jid trueCounterpart) {
368 this.trueCounterpart = trueCounterpart;
369 }
370
371 public Jid getTrueCounterpart() {
372 return this.trueCounterpart;
373 }
374
375 public Transferable getTransferable() {
376 return this.transferable;
377 }
378
379 public void setTransferable(Transferable transferable) {
380 this.transferable = transferable;
381 }
382
383 public boolean similar(Message message) {
384 if (type != TYPE_PRIVATE && this.serverMsgId != null && message.getServerMsgId() != null) {
385 return this.serverMsgId.equals(message.getServerMsgId());
386 } else if (this.body == null || this.counterpart == null) {
387 return false;
388 } else {
389 String body, otherBody;
390 if (this.hasFileOnRemoteHost()) {
391 body = getFileParams().url.toString();
392 otherBody = message.body == null ? null : message.body.trim();
393 } else {
394 body = this.body;
395 otherBody = message.body;
396 }
397 if (message.getRemoteMsgId() != null) {
398 return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid))
399 && this.counterpart.equals(message.getCounterpart())
400 && (body.equals(otherBody)
401 ||(message.getEncryption() == Message.ENCRYPTION_PGP
402 && CryptoHelper.UUID_PATTERN.matcher(message.getRemoteMsgId()).matches()));
403 } else {
404 return this.remoteMsgId == null
405 && this.counterpart.equals(message.getCounterpart())
406 && body.equals(otherBody)
407 && Math.abs(this.getTimeSent() - message.getTimeSent()) < Config.MESSAGE_MERGE_WINDOW * 1000;
408 }
409 }
410 }
411
412 public Message next() {
413 synchronized (this.conversation.messages) {
414 if (this.mNextMessage == null) {
415 int index = this.conversation.messages.indexOf(this);
416 if (index < 0 || index >= this.conversation.messages.size() - 1) {
417 this.mNextMessage = null;
418 } else {
419 this.mNextMessage = this.conversation.messages.get(index + 1);
420 }
421 }
422 return this.mNextMessage;
423 }
424 }
425
426 public Message prev() {
427 synchronized (this.conversation.messages) {
428 if (this.mPreviousMessage == null) {
429 int index = this.conversation.messages.indexOf(this);
430 if (index <= 0 || index > this.conversation.messages.size()) {
431 this.mPreviousMessage = null;
432 } else {
433 this.mPreviousMessage = this.conversation.messages.get(index - 1);
434 }
435 }
436 return this.mPreviousMessage;
437 }
438 }
439
440 public boolean isLastCorrectableMessage() {
441 Message next = next();
442 while(next != null) {
443 if (next.isCorrectable()) {
444 return false;
445 }
446 next = next.next();
447 }
448 return isCorrectable();
449 }
450
451 private boolean isCorrectable() {
452 return getStatus() != STATUS_RECEIVED && !isCarbon();
453 }
454
455 public boolean mergeable(final Message message) {
456 return message != null &&
457 (message.getType() == Message.TYPE_TEXT &&
458 this.getTransferable() == null &&
459 message.getTransferable() == null &&
460 message.getEncryption() != Message.ENCRYPTION_PGP &&
461 message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED &&
462 this.getType() == message.getType() &&
463 //this.getStatus() == message.getStatus() &&
464 isStatusMergeable(this.getStatus(), message.getStatus()) &&
465 this.getEncryption() == message.getEncryption() &&
466 this.getCounterpart() != null &&
467 this.getCounterpart().equals(message.getCounterpart()) &&
468 this.edited() == message.edited() &&
469 (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) &&
470 this.getBody().length() + message.getBody().length() <= Config.MAX_DISPLAY_MESSAGE_CHARS &&
471 !GeoHelper.isGeoUri(message.getBody()) &&
472 !GeoHelper.isGeoUri(this.body) &&
473 message.treatAsDownloadable() == Decision.NEVER &&
474 this.treatAsDownloadable() == Decision.NEVER &&
475 !message.getBody().startsWith(ME_COMMAND) &&
476 !this.getBody().startsWith(ME_COMMAND) &&
477 !this.bodyIsHeart() &&
478 !message.bodyIsHeart() &&
479 this.isTrusted() == message.isTrusted()
480 );
481 }
482
483 private static boolean isStatusMergeable(int a, int b) {
484 return a == b || (
485 (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_UNSEND)
486 || (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_SEND)
487 || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND)
488 || (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND_RECEIVED)
489 || (a == Message.STATUS_SEND && b == Message.STATUS_UNSEND)
490 || (a == Message.STATUS_SEND && b == Message.STATUS_SEND_RECEIVED)
491 );
492 }
493
494 public String getMergedBody() {
495 StringBuilder body = new StringBuilder(this.body.trim());
496 Message current = this;
497 while(current.mergeable(current.next())) {
498 current = current.next();
499 if (current == null) {
500 break;
501 }
502 body.append(MERGE_SEPARATOR);
503 body.append(current.getBody().trim());
504 }
505 return body.toString();
506 }
507
508 public boolean hasMeCommand() {
509 return getMergedBody().startsWith(ME_COMMAND);
510 }
511
512 public int getMergedStatus() {
513 int status = this.status;
514 Message current = this;
515 while(current.mergeable(current.next())) {
516 current = current.next();
517 if (current == null) {
518 break;
519 }
520 status = current.status;
521 }
522 return status;
523 }
524
525 public long getMergedTimeSent() {
526 long time = this.timeSent;
527 Message current = this;
528 while(current.mergeable(current.next())) {
529 current = current.next();
530 if (current == null) {
531 break;
532 }
533 time = current.timeSent;
534 }
535 return time;
536 }
537
538 public boolean wasMergedIntoPrevious() {
539 Message prev = this.prev();
540 return prev != null && prev.mergeable(this);
541 }
542
543 public boolean trusted() {
544 Contact contact = this.getContact();
545 return (status > STATUS_RECEIVED || (contact != null && contact.mutualPresenceSubscription()));
546 }
547
548 public boolean fixCounterpart() {
549 Presences presences = conversation.getContact().getPresences();
550 if (counterpart != null && presences.has(counterpart.getResourcepart())) {
551 return true;
552 } else if (presences.size() >= 1) {
553 try {
554 counterpart = Jid.fromParts(conversation.getJid().getLocalpart(),
555 conversation.getJid().getDomainpart(),
556 presences.toResourceArray()[0]);
557 return true;
558 } catch (InvalidJidException e) {
559 counterpart = null;
560 return false;
561 }
562 } else {
563 counterpart = null;
564 return false;
565 }
566 }
567
568 public void setUuid(String uuid) {
569 this.uuid = uuid;
570 }
571
572 public String getEditedId() {
573 return edited;
574 }
575
576 public void setOob(boolean isOob) {
577 this.oob = isOob;
578 }
579
580 public enum Decision {
581 MUST,
582 SHOULD,
583 NEVER,
584 }
585
586 private static String extractRelevantExtension(URL url) {
587 String path = url.getPath();
588 return extractRelevantExtension(path);
589 }
590
591 private static String extractRelevantExtension(String path) {
592 if (path == null || path.isEmpty()) {
593 return null;
594 }
595
596 String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase();
597 int dotPosition = filename.lastIndexOf(".");
598
599 if (dotPosition != -1) {
600 String extension = filename.substring(dotPosition + 1);
601 // we want the real file extension, not the crypto one
602 if (Transferable.VALID_CRYPTO_EXTENSIONS.contains(extension)) {
603 return extractRelevantExtension(filename.substring(0,dotPosition));
604 } else {
605 return extension;
606 }
607 }
608 return null;
609 }
610
611 public String getMimeType() {
612 if (relativeFilePath != null) {
613 int start = relativeFilePath.lastIndexOf('.') + 1;
614 if (start < relativeFilePath.length()) {
615 return MimeUtils.guessMimeTypeFromExtension(relativeFilePath.substring(start));
616 } else {
617 return null;
618 }
619 } else {
620 try {
621 return MimeUtils.guessMimeTypeFromExtension(extractRelevantExtension(new URL(body.trim())));
622 } catch (MalformedURLException e) {
623 return null;
624 }
625 }
626 }
627
628 public Decision treatAsDownloadable() {
629 if (body.trim().contains(" ")) {
630 return Decision.NEVER;
631 }
632 try {
633 URL url = new URL(body);
634 if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
635 return Decision.NEVER;
636 } else if (oob) {
637 return Decision.MUST;
638 }
639 String extension = extractRelevantExtension(url);
640 if (extension == null) {
641 return Decision.NEVER;
642 }
643 String ref = url.getRef();
644 boolean encrypted = ref != null && ref.matches("([A-Fa-f0-9]{2}){48}");
645
646 if (encrypted) {
647 return Decision.MUST;
648 } else if (Transferable.VALID_IMAGE_EXTENSIONS.contains(extension)
649 || Transferable.WELL_KNOWN_EXTENSIONS.contains(extension)) {
650 return Decision.SHOULD;
651 } else {
652 return Decision.NEVER;
653 }
654
655 } catch (MalformedURLException e) {
656 return Decision.NEVER;
657 }
658 }
659
660 public boolean bodyIsHeart() {
661 return body != null && UIHelper.HEARTS.contains(body.trim());
662 }
663
664 public FileParams getFileParams() {
665 FileParams params = getLegacyFileParams();
666 if (params != null) {
667 return params;
668 }
669 params = new FileParams();
670 if (this.transferable != null) {
671 params.size = this.transferable.getFileSize();
672 }
673 if (body == null) {
674 return params;
675 }
676 String parts[] = body.split("\\|");
677 switch (parts.length) {
678 case 1:
679 try {
680 params.size = Long.parseLong(parts[0]);
681 } catch (NumberFormatException e) {
682 try {
683 params.url = new URL(parts[0]);
684 } catch (MalformedURLException e1) {
685 params.url = null;
686 }
687 }
688 break;
689 case 2:
690 case 4:
691 try {
692 params.url = new URL(parts[0]);
693 } catch (MalformedURLException e1) {
694 params.url = null;
695 }
696 try {
697 params.size = Long.parseLong(parts[1]);
698 } catch (NumberFormatException e) {
699 params.size = 0;
700 }
701 try {
702 params.width = Integer.parseInt(parts[2]);
703 } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
704 params.width = 0;
705 }
706 try {
707 params.height = Integer.parseInt(parts[3]);
708 } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
709 params.height = 0;
710 }
711 break;
712 case 3:
713 try {
714 params.size = Long.parseLong(parts[0]);
715 } catch (NumberFormatException e) {
716 params.size = 0;
717 }
718 try {
719 params.width = Integer.parseInt(parts[1]);
720 } catch (NumberFormatException e) {
721 params.width = 0;
722 }
723 try {
724 params.height = Integer.parseInt(parts[2]);
725 } catch (NumberFormatException e) {
726 params.height = 0;
727 }
728 break;
729 }
730 return params;
731 }
732
733 public FileParams getLegacyFileParams() {
734 FileParams params = new FileParams();
735 if (body == null) {
736 return params;
737 }
738 String parts[] = body.split(",");
739 if (parts.length == 3) {
740 try {
741 params.size = Long.parseLong(parts[0]);
742 } catch (NumberFormatException e) {
743 return null;
744 }
745 try {
746 params.width = Integer.parseInt(parts[1]);
747 } catch (NumberFormatException e) {
748 return null;
749 }
750 try {
751 params.height = Integer.parseInt(parts[2]);
752 } catch (NumberFormatException e) {
753 return null;
754 }
755 return params;
756 } else {
757 return null;
758 }
759 }
760
761 public void untie() {
762 this.mNextMessage = null;
763 this.mPreviousMessage = null;
764 }
765
766 public boolean isFileOrImage() {
767 return type == TYPE_FILE || type == TYPE_IMAGE;
768 }
769
770 public boolean hasFileOnRemoteHost() {
771 return isFileOrImage() && getFileParams().url != null;
772 }
773
774 public boolean needsUploading() {
775 return isFileOrImage() && getFileParams().url == null;
776 }
777
778 public class FileParams {
779 public URL url;
780 public long size = 0;
781 public int width = 0;
782 public int height = 0;
783 }
784
785 public void setFingerprint(String fingerprint) {
786 this.axolotlFingerprint = fingerprint;
787 }
788
789 public String getFingerprint() {
790 return axolotlFingerprint;
791 }
792
793 public boolean isTrusted() {
794 XmppAxolotlSession.Trust t = conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint);
795 return t != null && t.trusted();
796 }
797
798 private int getPreviousEncryption() {
799 for (Message iterator = this.prev(); iterator != null; iterator = iterator.prev()){
800 if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) {
801 continue;
802 }
803 return iterator.getEncryption();
804 }
805 return ENCRYPTION_NONE;
806 }
807
808 private int getNextEncryption() {
809 for (Message iterator = this.next(); iterator != null; iterator = iterator.next()){
810 if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) {
811 continue;
812 }
813 return iterator.getEncryption();
814 }
815 return conversation.getNextEncryption();
816 }
817
818 public boolean isValidInSession() {
819 int pastEncryption = getCleanedEncryption(this.getPreviousEncryption());
820 int futureEncryption = getCleanedEncryption(this.getNextEncryption());
821
822 boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE
823 || futureEncryption == ENCRYPTION_NONE
824 || pastEncryption != futureEncryption;
825
826 return inUnencryptedSession || getCleanedEncryption(this.getEncryption()) == pastEncryption;
827 }
828
829 private static int getCleanedEncryption(int encryption) {
830 if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) {
831 return ENCRYPTION_PGP;
832 }
833 return encryption;
834 }
835}