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