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