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