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