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