Message.java

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