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