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