1package eu.siacs.conversations.entities;
2
3import android.content.ContentValues;
4import android.database.Cursor;
5
6import java.net.MalformedURLException;
7import java.net.URL;
8import java.util.Arrays;
9
10import eu.siacs.conversations.Config;
11import eu.siacs.conversations.xmpp.jid.InvalidJidException;
12import eu.siacs.conversations.xmpp.jid.Jid;
13
14public class Message extends AbstractEntity {
15
16 public static final String TABLENAME = "messages";
17
18 public static final int STATUS_RECEIVED = 0;
19 public static final int STATUS_UNSEND = 1;
20 public static final int STATUS_SEND = 2;
21 public static final int STATUS_SEND_FAILED = 3;
22 public static final int STATUS_WAITING = 5;
23 public static final int STATUS_OFFERED = 6;
24 public static final int STATUS_SEND_RECEIVED = 7;
25 public static final int STATUS_SEND_DISPLAYED = 8;
26
27 public static final int ENCRYPTION_NONE = 0;
28 public static final int ENCRYPTION_PGP = 1;
29 public static final int ENCRYPTION_OTR = 2;
30 public static final int ENCRYPTION_DECRYPTED = 3;
31 public static final int ENCRYPTION_DECRYPTION_FAILED = 4;
32
33 public static final int TYPE_TEXT = 0;
34 public static final int TYPE_IMAGE = 1;
35 public static final int TYPE_FILE = 2;
36 public static final int TYPE_STATUS = 3;
37 public static final int TYPE_PRIVATE = 4;
38
39 public static String CONVERSATION = "conversationUuid";
40 public static String COUNTERPART = "counterpart";
41 public static String TRUE_COUNTERPART = "trueCounterpart";
42 public static String BODY = "body";
43 public static String TIME_SENT = "timeSent";
44 public static String ENCRYPTION = "encryption";
45 public static String STATUS = "status";
46 public static String TYPE = "type";
47 public static String REMOTE_MSG_ID = "remoteMsgId";
48 public static String RELATIVE_FILE_PATH = "relativeFilePath";
49 public boolean markable = false;
50 protected String conversationUuid;
51 protected Jid counterpart;
52 protected Jid trueCounterpart;
53 protected String body;
54 protected String encryptedBody;
55 protected long timeSent;
56 protected int encryption;
57 protected int status;
58 protected int type;
59 protected String relativeFilePath;
60 protected boolean read = true;
61 protected String remoteMsgId = null;
62 protected Conversation conversation = null;
63 protected Downloadable downloadable = null;
64 private Message mNextMessage = null;
65 private Message mPreviousMessage = null;
66
67 private Message() {
68
69 }
70
71 public Message(Conversation conversation, String body, int encryption) {
72 this(conversation,body,encryption,STATUS_UNSEND);
73 }
74
75 public Message(Conversation conversation, String body, int encryption, int status) {
76 this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
77 conversation.getContactJid().toBareJid(), null, body, System
78 .currentTimeMillis(), encryption,
79 status, TYPE_TEXT, null,null);
80 this.conversation = conversation;
81 }
82
83 public Message(final String uuid, final String conversationUUid, final Jid counterpart,
84 final Jid trueCounterpart, final String body, final long timeSent,
85 final int encryption, final int status, final int type, final String remoteMsgId, final String relativeFilePath) {
86 this.uuid = uuid;
87 this.conversationUuid = conversationUUid;
88 this.counterpart = counterpart;
89 this.trueCounterpart = trueCounterpart;
90 this.body = body;
91 this.timeSent = timeSent;
92 this.encryption = encryption;
93 this.status = status;
94 this.type = type;
95 this.remoteMsgId = remoteMsgId;
96 this.relativeFilePath = relativeFilePath;
97 }
98
99 public static Message fromCursor(Cursor cursor) {
100 Jid jid;
101 try {
102 String value = cursor.getString(cursor.getColumnIndex(COUNTERPART));
103 if (value!=null) {
104 jid = Jid.fromString(value);
105 } else {
106 jid = null;
107 }
108 } catch (InvalidJidException e) {
109 jid = null;
110 }
111 Jid trueCounterpart;
112 try {
113 String value = cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART));
114 if (value!=null) {
115 trueCounterpart = Jid.fromString(value);
116 } else {
117 trueCounterpart = null;
118 }
119 } catch (InvalidJidException e) {
120 trueCounterpart = null;
121 }
122 return new Message(cursor.getString(cursor.getColumnIndex(UUID)),
123 cursor.getString(cursor.getColumnIndex(CONVERSATION)),
124 jid,
125 trueCounterpart,
126 cursor.getString(cursor.getColumnIndex(BODY)),
127 cursor.getLong(cursor.getColumnIndex(TIME_SENT)),
128 cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
129 cursor.getInt(cursor.getColumnIndex(STATUS)),
130 cursor.getInt(cursor.getColumnIndex(TYPE)),
131 cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)),
132 cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)));
133 }
134
135 public static Message createStatusMessage(Conversation conversation) {
136 Message message = new Message();
137 message.setType(Message.TYPE_STATUS);
138 message.setConversation(conversation);
139 return message;
140 }
141
142 @Override
143 public ContentValues getContentValues() {
144 ContentValues values = new ContentValues();
145 values.put(UUID, uuid);
146 values.put(CONVERSATION, conversationUuid);
147 if (counterpart == null) {
148 values.putNull(COUNTERPART);
149 } else {
150 values.put(COUNTERPART, counterpart.toString());
151 }
152 if (trueCounterpart == null ) {
153 values.putNull(TRUE_COUNTERPART);
154 } else {
155 values.put(TRUE_COUNTERPART, trueCounterpart.toString());
156 }
157 values.put(BODY, body);
158 values.put(TIME_SENT, timeSent);
159 values.put(ENCRYPTION, encryption);
160 values.put(STATUS, status);
161 values.put(TYPE, type);
162 values.put(REMOTE_MSG_ID, remoteMsgId);
163 values.put(RELATIVE_FILE_PATH, relativeFilePath);
164 return values;
165 }
166
167 public String getConversationUuid() {
168 return conversationUuid;
169 }
170
171 public Conversation getConversation() {
172 return this.conversation;
173 }
174
175 public void setConversation(Conversation conv) {
176 this.conversation = conv;
177 }
178
179 public Jid getCounterpart() {
180 return counterpart;
181 }
182
183 public void setCounterpart(final Jid counterpart) {
184 this.counterpart = counterpart;
185 }
186
187 public Contact getContact() {
188 if (this.conversation.getMode() == Conversation.MODE_SINGLE) {
189 return this.conversation.getContact();
190 } else {
191 if (this.trueCounterpart == null) {
192 return null;
193 } else {
194 return this.conversation.getAccount().getRoster()
195 .getContactFromRoster(this.trueCounterpart);
196 }
197 }
198 }
199
200 public String getBody() {
201 return body;
202 }
203
204 public void setBody(String body) {
205 this.body = body;
206 }
207
208 public long getTimeSent() {
209 return timeSent;
210 }
211
212 public int getEncryption() {
213 return encryption;
214 }
215
216 public void setEncryption(int encryption) {
217 this.encryption = encryption;
218 }
219
220 public int getStatus() {
221 return status;
222 }
223
224 public void setStatus(int status) {
225 this.status = status;
226 }
227
228 public void setRelativeFilePath(String path) {
229 this.relativeFilePath = path;
230 }
231
232 public String getRelativeFilePath() {
233 return this.relativeFilePath;
234 }
235
236 public String getRemoteMsgId() {
237 return this.remoteMsgId;
238 }
239
240 public void setRemoteMsgId(String id) {
241 this.remoteMsgId = id;
242 }
243
244 public boolean isRead() {
245 return this.read;
246 }
247
248 public void markRead() {
249 this.read = true;
250 }
251
252 public void markUnread() {
253 this.read = false;
254 }
255
256 public void setTime(long time) {
257 this.timeSent = time;
258 }
259
260 public String getEncryptedBody() {
261 return this.encryptedBody;
262 }
263
264 public void setEncryptedBody(String body) {
265 this.encryptedBody = body;
266 }
267
268 public int getType() {
269 return this.type;
270 }
271
272 public void setType(int type) {
273 this.type = type;
274 }
275
276 public void setTrueCounterpart(Jid trueCounterpart) {
277 this.trueCounterpart = trueCounterpart;
278 }
279
280 public Downloadable getDownloadable() {
281 return this.downloadable;
282 }
283
284 public void setDownloadable(Downloadable downloadable) {
285 this.downloadable = downloadable;
286 }
287
288 public boolean equals(Message message) {
289 if ((this.remoteMsgId != null) && (this.body != null)
290 && (this.counterpart != null)) {
291 return this.remoteMsgId.equals(message.getRemoteMsgId())
292 && this.body.equals(message.getBody())
293 && this.counterpart.equals(message.getCounterpart());
294 } else {
295 return false;
296 }
297 }
298
299 public Message next() {
300 if (this.mNextMessage == null) {
301 synchronized (this.conversation.messages) {
302 int index = this.conversation.messages.indexOf(this);
303 if (index < 0
304 || index >= this.conversation.getMessages().size() - 1) {
305 this.mNextMessage = null;
306 } else {
307 this.mNextMessage = this.conversation.messages
308 .get(index + 1);
309 }
310 }
311 }
312 return this.mNextMessage;
313 }
314
315 public Message prev() {
316 if (this.mPreviousMessage == null) {
317 synchronized (this.conversation.messages) {
318 int index = this.conversation.messages.indexOf(this);
319 if (index <= 0 || index > this.conversation.messages.size()) {
320 this.mPreviousMessage = null;
321 } else {
322 this.mPreviousMessage = this.conversation.messages
323 .get(index - 1);
324 }
325 }
326 }
327 return this.mPreviousMessage;
328 }
329
330 public boolean mergeable(Message message) {
331 if (message == null) {
332 return false;
333 }
334 return (message.getType() == Message.TYPE_TEXT
335 && this.getDownloadable() == null
336 && message.getDownloadable() == null
337 && message.getEncryption() != Message.ENCRYPTION_PGP
338 && this.getType() == message.getType()
339 && this.getEncryption() == message.getEncryption()
340 && this.getCounterpart() != null
341 && this.getCounterpart().equals(message.getCounterpart())
342 && (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && ((this
343 .getStatus() == message.getStatus() || ((this.getStatus() == Message.STATUS_SEND || this
344 .getStatus() == Message.STATUS_SEND_RECEIVED) && (message
345 .getStatus() == Message.STATUS_UNSEND
346 || message.getStatus() == Message.STATUS_SEND || message
347 .getStatus() == Message.STATUS_SEND_DISPLAYED))))
348 && !message.bodyContainsDownloadable()
349 && !this.bodyContainsDownloadable());
350 }
351
352 public String getMergedBody() {
353 Message next = this.next();
354 if (this.mergeable(next)) {
355 return body.trim() + '\n' + next.getMergedBody();
356 }
357 return body.trim();
358 }
359
360 public int getMergedStatus() {
361 Message next = this.next();
362 if (this.mergeable(next)) {
363 return next.getMergedStatus();
364 } else {
365 return getStatus();
366 }
367 }
368
369 public long getMergedTimeSent() {
370 Message next = this.next();
371 if (this.mergeable(next)) {
372 return next.getMergedTimeSent();
373 } else {
374 return getTimeSent();
375 }
376 }
377
378 public boolean wasMergedIntoPrevious() {
379 Message prev = this.prev();
380 return prev != null && prev.mergeable(this);
381 }
382
383 public boolean trusted() {
384 Contact contact = this.getContact();
385 return (status > STATUS_RECEIVED || (contact != null && contact.trusted()));
386 }
387
388 public boolean bodyContainsDownloadable() {
389 try {
390 URL url = new URL(this.getBody());
391 if (!url.getProtocol().equalsIgnoreCase("http")
392 && !url.getProtocol().equalsIgnoreCase("https")) {
393 return false;
394 }
395 if (url.getPath() == null) {
396 return false;
397 }
398 String[] pathParts = url.getPath().split("/");
399 String filename;
400 if (pathParts.length > 0) {
401 filename = pathParts[pathParts.length - 1];
402 } else {
403 return false;
404 }
405 String[] extensionParts = filename.split("\\.");
406 if (extensionParts.length == 2
407 && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
408 extensionParts[extensionParts.length - 1])) {
409 return true;
410 } else if (extensionParts.length == 3
411 && Arrays
412 .asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
413 .contains(extensionParts[extensionParts.length - 1])
414 && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
415 extensionParts[extensionParts.length - 2])) {
416 return true;
417 } else {
418 return false;
419 }
420 } catch (MalformedURLException e) {
421 return false;
422 }
423 }
424
425 public ImageParams getImageParams() {
426 ImageParams params = getLegacyImageParams();
427 if (params != null) {
428 return params;
429 }
430 params = new ImageParams();
431 if (this.downloadable != null) {
432 params.size = this.downloadable.getFileSize();
433 }
434 if (body == null) {
435 return params;
436 }
437 String parts[] = body.split("\\|");
438 if (parts.length == 1) {
439 try {
440 params.size = Long.parseLong(parts[0]);
441 } catch (NumberFormatException e) {
442 params.origin = parts[0];
443 try {
444 params.url = new URL(parts[0]);
445 } catch (MalformedURLException e1) {
446 params.url = null;
447 }
448 }
449 } else if (parts.length == 3) {
450 try {
451 params.size = Long.parseLong(parts[0]);
452 } catch (NumberFormatException e) {
453 params.size = 0;
454 }
455 try {
456 params.width = Integer.parseInt(parts[1]);
457 } catch (NumberFormatException e) {
458 params.width = 0;
459 }
460 try {
461 params.height = Integer.parseInt(parts[2]);
462 } catch (NumberFormatException e) {
463 params.height = 0;
464 }
465 } else if (parts.length == 4) {
466 params.origin = parts[0];
467 try {
468 params.url = new URL(parts[0]);
469 } catch (MalformedURLException e1) {
470 params.url = null;
471 }
472 try {
473 params.size = Long.parseLong(parts[1]);
474 } catch (NumberFormatException e) {
475 params.size = 0;
476 }
477 try {
478 params.width = Integer.parseInt(parts[2]);
479 } catch (NumberFormatException e) {
480 params.width = 0;
481 }
482 try {
483 params.height = Integer.parseInt(parts[3]);
484 } catch (NumberFormatException e) {
485 params.height = 0;
486 }
487 }
488 return params;
489 }
490
491 public ImageParams getLegacyImageParams() {
492 ImageParams params = new ImageParams();
493 if (body == null) {
494 return params;
495 }
496 String parts[] = body.split(",");
497 if (parts.length == 3) {
498 try {
499 params.size = Long.parseLong(parts[0]);
500 } catch (NumberFormatException e) {
501 return null;
502 }
503 try {
504 params.width = Integer.parseInt(parts[1]);
505 } catch (NumberFormatException e) {
506 return null;
507 }
508 try {
509 params.height = Integer.parseInt(parts[2]);
510 } catch (NumberFormatException e) {
511 return null;
512 }
513 return params;
514 } else {
515 return null;
516 }
517 }
518
519 public class ImageParams {
520 public URL url;
521 public long size = 0;
522 public int width = 0;
523 public int height = 0;
524 public String origin;
525 }
526}