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 String getRelativeFilePath() {
229 return this.relativeFilePath;
230 }
231
232 public void setRelativeFilePath(String path) {
233 this.relativeFilePath = path;
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 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());
290 }
291
292 public Message next() {
293 if (this.mNextMessage == null) {
294 synchronized (this.conversation.messages) {
295 int index = this.conversation.messages.indexOf(this);
296 if (index < 0
297 || index >= this.conversation.getMessages().size() - 1) {
298 this.mNextMessage = null;
299 } else {
300 this.mNextMessage = this.conversation.messages
301 .get(index + 1);
302 }
303 }
304 }
305 return this.mNextMessage;
306 }
307
308 public Message prev() {
309 if (this.mPreviousMessage == null) {
310 synchronized (this.conversation.messages) {
311 int index = this.conversation.messages.indexOf(this);
312 if (index <= 0 || index > this.conversation.messages.size()) {
313 this.mPreviousMessage = null;
314 } else {
315 this.mPreviousMessage = this.conversation.messages
316 .get(index - 1);
317 }
318 }
319 }
320 return this.mPreviousMessage;
321 }
322
323 public boolean mergeable(final Message message) {
324 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());
325 }
326
327 public String getMergedBody() {
328 Message next = this.next();
329 if (this.mergeable(next)) {
330 return body.trim() + '\n' + next.getMergedBody();
331 }
332 return body.trim();
333 }
334
335 public int getMergedStatus() {
336 return getStatus();
337 }
338
339 public long getMergedTimeSent() {
340 Message next = this.next();
341 if (this.mergeable(next)) {
342 return next.getMergedTimeSent();
343 } else {
344 return getTimeSent();
345 }
346 }
347
348 public boolean wasMergedIntoPrevious() {
349 Message prev = this.prev();
350 return prev != null && prev.mergeable(this);
351 }
352
353 public boolean trusted() {
354 Contact contact = this.getContact();
355 return (status > STATUS_RECEIVED || (contact != null && contact.trusted()));
356 }
357
358 public boolean bodyContainsDownloadable() {
359 try {
360 URL url = new URL(this.getBody());
361 if (!url.getProtocol().equalsIgnoreCase("http")
362 && !url.getProtocol().equalsIgnoreCase("https")) {
363 return false;
364 }
365 if (url.getPath() == null) {
366 return false;
367 }
368 String[] pathParts = url.getPath().split("/");
369 String filename;
370 if (pathParts.length > 0) {
371 filename = pathParts[pathParts.length - 1];
372 } else {
373 return false;
374 }
375 String[] extensionParts = filename.split("\\.");
376 if (extensionParts.length == 2
377 && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
378 extensionParts[extensionParts.length - 1])) {
379 return true;
380 } else if (extensionParts.length == 3
381 && Arrays
382 .asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
383 .contains(extensionParts[extensionParts.length - 1])
384 && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
385 extensionParts[extensionParts.length - 2])) {
386 return true;
387 } else {
388 return false;
389 }
390 } catch (MalformedURLException e) {
391 return false;
392 }
393 }
394
395 public ImageParams getImageParams() {
396 ImageParams params = getLegacyImageParams();
397 if (params != null) {
398 return params;
399 }
400 params = new ImageParams();
401 if (this.downloadable != null) {
402 params.size = this.downloadable.getFileSize();
403 }
404 if (body == null) {
405 return params;
406 }
407 String parts[] = body.split("\\|");
408 if (parts.length == 1) {
409 try {
410 params.size = Long.parseLong(parts[0]);
411 } catch (NumberFormatException e) {
412 params.origin = parts[0];
413 try {
414 params.url = new URL(parts[0]);
415 } catch (MalformedURLException e1) {
416 params.url = null;
417 }
418 }
419 } else if (parts.length == 3) {
420 try {
421 params.size = Long.parseLong(parts[0]);
422 } catch (NumberFormatException e) {
423 params.size = 0;
424 }
425 try {
426 params.width = Integer.parseInt(parts[1]);
427 } catch (NumberFormatException e) {
428 params.width = 0;
429 }
430 try {
431 params.height = Integer.parseInt(parts[2]);
432 } catch (NumberFormatException e) {
433 params.height = 0;
434 }
435 } else if (parts.length == 4) {
436 params.origin = parts[0];
437 try {
438 params.url = new URL(parts[0]);
439 } catch (MalformedURLException e1) {
440 params.url = null;
441 }
442 try {
443 params.size = Long.parseLong(parts[1]);
444 } catch (NumberFormatException e) {
445 params.size = 0;
446 }
447 try {
448 params.width = Integer.parseInt(parts[2]);
449 } catch (NumberFormatException e) {
450 params.width = 0;
451 }
452 try {
453 params.height = Integer.parseInt(parts[3]);
454 } catch (NumberFormatException e) {
455 params.height = 0;
456 }
457 }
458 return params;
459 }
460
461 public ImageParams getLegacyImageParams() {
462 ImageParams params = new ImageParams();
463 if (body == null) {
464 return params;
465 }
466 String parts[] = body.split(",");
467 if (parts.length == 3) {
468 try {
469 params.size = Long.parseLong(parts[0]);
470 } catch (NumberFormatException e) {
471 return null;
472 }
473 try {
474 params.width = Integer.parseInt(parts[1]);
475 } catch (NumberFormatException e) {
476 return null;
477 }
478 try {
479 params.height = Integer.parseInt(parts[2]);
480 } catch (NumberFormatException e) {
481 return null;
482 }
483 return params;
484 } else {
485 return null;
486 }
487 }
488
489 public class ImageParams {
490 public URL url;
491 public long size = 0;
492 public int width = 0;
493 public int height = 0;
494 public String origin;
495 }
496}