Message.java

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