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