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