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