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