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