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 Jid getPresence() {
252 // TODO: This is now the same as getCounterpart()... find usages in code and remove one?
253 return counterpart;
254 }
255
256 public void setDownloadable(Downloadable downloadable) {
257 this.downloadable = downloadable;
258 }
259
260 public Downloadable getDownloadable() {
261 return this.downloadable;
262 }
263
264 public static Message createStatusMessage(Conversation conversation) {
265 Message message = new Message();
266 message.setType(Message.TYPE_STATUS);
267 message.setConversation(conversation);
268 return message;
269 }
270
271 public void setCounterpart(final Jid counterpart) {
272 this.counterpart = counterpart;
273 }
274
275 public boolean equals(Message message) {
276 if ((this.remoteMsgId != null) && (this.body != null)
277 && (this.counterpart != null)) {
278 return this.remoteMsgId.equals(message.getRemoteMsgId())
279 && this.body.equals(message.getBody())
280 && this.counterpart.equals(message.getCounterpart());
281 } else {
282 return false;
283 }
284 }
285
286 public Message next() {
287 if (this.mNextMessage == null) {
288 synchronized (this.conversation.messages) {
289 int index = this.conversation.messages.indexOf(this);
290 if (index < 0
291 || index >= this.conversation.getMessages().size() - 1) {
292 this.mNextMessage = null;
293 } else {
294 this.mNextMessage = this.conversation.messages
295 .get(index + 1);
296 }
297 }
298 }
299 return this.mNextMessage;
300 }
301
302 public Message prev() {
303 if (this.mPreviousMessage == null) {
304 synchronized (this.conversation.messages) {
305 int index = this.conversation.messages.indexOf(this);
306 if (index <= 0 || index > this.conversation.messages.size()) {
307 this.mPreviousMessage = null;
308 } else {
309 this.mPreviousMessage = this.conversation.messages
310 .get(index - 1);
311 }
312 }
313 }
314 return this.mPreviousMessage;
315 }
316
317 public boolean mergeable(Message message) {
318 if (message == null) {
319 return false;
320 }
321 return (message.getType() == Message.TYPE_TEXT
322 && this.getDownloadable() == null
323 && message.getDownloadable() == null
324 && message.getEncryption() != Message.ENCRYPTION_PGP
325 && this.getType() == message.getType()
326 && this.getEncryption() == message.getEncryption()
327 && this.getCounterpart().equals(message.getCounterpart())
328 && (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && ((this
329 .getStatus() == message.getStatus() || ((this.getStatus() == Message.STATUS_SEND || this
330 .getStatus() == Message.STATUS_SEND_RECEIVED) && (message
331 .getStatus() == Message.STATUS_UNSEND
332 || message.getStatus() == Message.STATUS_SEND || message
333 .getStatus() == Message.STATUS_SEND_DISPLAYED))))
334 && !message.bodyContainsDownloadable()
335 && !this.bodyContainsDownloadable());
336 }
337
338 public String getMergedBody() {
339 Message next = this.next();
340 if (this.mergeable(next)) {
341 return body.trim() + '\n' + next.getMergedBody();
342 }
343 return body.trim();
344 }
345
346 public int getMergedStatus() {
347 Message next = this.next();
348 if (this.mergeable(next)) {
349 return next.getMergedStatus();
350 } else {
351 return getStatus();
352 }
353 }
354
355 public long getMergedTimeSent() {
356 Message next = this.next();
357 if (this.mergeable(next)) {
358 return next.getMergedTimeSent();
359 } else {
360 return getTimeSent();
361 }
362 }
363
364 public boolean wasMergedIntoPrevious() {
365 Message prev = this.prev();
366 return prev != null && prev.mergeable(this);
367 }
368
369 public boolean trusted() {
370 Contact contact = this.getContact();
371 return (status > STATUS_RECEIVED || (contact != null && contact.trusted()));
372 }
373
374 public boolean bodyContainsDownloadable() {
375 try {
376 URL url = new URL(this.getBody());
377 if (!url.getProtocol().equalsIgnoreCase("http")
378 && !url.getProtocol().equalsIgnoreCase("https")) {
379 return false;
380 }
381 if (url.getPath() == null) {
382 return false;
383 }
384 String[] pathParts = url.getPath().split("/");
385 String filename;
386 if (pathParts.length > 0) {
387 filename = pathParts[pathParts.length - 1];
388 } else {
389 return false;
390 }
391 String[] extensionParts = filename.split("\\.");
392 if (extensionParts.length == 2
393 && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
394 extensionParts[extensionParts.length - 1])) {
395 return true;
396 } else if (extensionParts.length == 3
397 && Arrays
398 .asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
399 .contains(extensionParts[extensionParts.length - 1])
400 && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
401 extensionParts[extensionParts.length - 2])) {
402 return true;
403 } else {
404 return false;
405 }
406 } catch (MalformedURLException e) {
407 return false;
408 }
409 }
410
411 public ImageParams getImageParams() {
412 ImageParams params = getLegacyImageParams();
413 if (params!=null) {
414 return params;
415 }
416 params = new ImageParams();
417 if (this.downloadable != null) {
418 params.size = this.downloadable.getFileSize();
419 }
420 if (body == null) {
421 return params;
422 }
423 String parts[] = body.split("\\|");
424 if (parts.length == 1) {
425 try {
426 params.size = Long.parseLong(parts[0]);
427 } catch (NumberFormatException e) {
428 params.origin = parts[0];
429 try {
430 params.url = new URL(parts[0]);
431 } catch (MalformedURLException e1) {
432 params.url = null;
433 }
434 }
435 } else if (parts.length == 3) {
436 try {
437 params.size = Long.parseLong(parts[0]);
438 } catch (NumberFormatException e) {
439 params.size = 0;
440 }
441 try {
442 params.width = Integer.parseInt(parts[1]);
443 } catch (NumberFormatException e) {
444 params.width = 0;
445 }
446 try {
447 params.height = Integer.parseInt(parts[2]);
448 } catch (NumberFormatException e) {
449 params.height = 0;
450 }
451 } else if (parts.length == 4) {
452 params.origin = parts[0];
453 try {
454 params.url = new URL(parts[0]);
455 } catch (MalformedURLException e1) {
456 params.url = null;
457 }
458 try {
459 params.size = Long.parseLong(parts[1]);
460 } catch (NumberFormatException e) {
461 params.size = 0;
462 }
463 try {
464 params.width = Integer.parseInt(parts[2]);
465 } catch (NumberFormatException e) {
466 params.width = 0;
467 }
468 try {
469 params.height = Integer.parseInt(parts[3]);
470 } catch (NumberFormatException e) {
471 params.height = 0;
472 }
473 }
474 return params;
475 }
476
477 public ImageParams getLegacyImageParams() {
478 ImageParams params = new ImageParams();
479 if (body == null) {
480 return params;
481 }
482 String parts[] = body.split(",");
483 if (parts.length == 3) {
484 try {
485 params.size = Long.parseLong(parts[0]);
486 } catch (NumberFormatException e) {
487 return null;
488 }
489 try {
490 params.width = Integer.parseInt(parts[1]);
491 } catch (NumberFormatException e) {
492 return null;
493 }
494 try {
495 params.height = Integer.parseInt(parts[2]);
496 } catch (NumberFormatException e) {
497 return null;
498 }
499 return params;
500 } else {
501 return null;
502 }
503 }
504
505 public class ImageParams {
506 public URL url;
507 public long size = 0;
508 public int width = 0;
509 public int height = 0;
510 public String origin;
511 }
512}