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