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