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 setTrueCounterpart(String trueCounterpart) {
236 this.trueCounterpart = trueCounterpart;
237 }
238
239 public void setDownloadable(Downloadable downloadable) {
240 this.downloadable = downloadable;
241 }
242
243 public Downloadable getDownloadable() {
244 return this.downloadable;
245 }
246
247 public static Message createStatusMessage(Conversation conversation) {
248 Message message = new Message();
249 message.setType(Message.TYPE_STATUS);
250 message.setConversation(conversation);
251 return message;
252 }
253
254 public void setCounterpart(final Jid counterpart) {
255 this.counterpart = counterpart;
256 }
257
258 public boolean equals(Message message) {
259 if ((this.remoteMsgId != null) && (this.body != null)
260 && (this.counterpart != null)) {
261 return this.remoteMsgId.equals(message.getRemoteMsgId())
262 && this.body.equals(message.getBody())
263 && this.counterpart.equals(message.getCounterpart());
264 } else {
265 return false;
266 }
267 }
268
269 public Message next() {
270 if (this.mNextMessage == null) {
271 synchronized (this.conversation.messages) {
272 int index = this.conversation.messages.indexOf(this);
273 if (index < 0
274 || index >= this.conversation.getMessages().size() - 1) {
275 this.mNextMessage = null;
276 } else {
277 this.mNextMessage = this.conversation.messages
278 .get(index + 1);
279 }
280 }
281 }
282 return this.mNextMessage;
283 }
284
285 public Message prev() {
286 if (this.mPreviousMessage == null) {
287 synchronized (this.conversation.messages) {
288 int index = this.conversation.messages.indexOf(this);
289 if (index <= 0 || index > this.conversation.messages.size()) {
290 this.mPreviousMessage = null;
291 } else {
292 this.mPreviousMessage = this.conversation.messages
293 .get(index - 1);
294 }
295 }
296 }
297 return this.mPreviousMessage;
298 }
299
300 public boolean mergeable(Message message) {
301 if (message == null) {
302 return false;
303 }
304 return (message.getType() == Message.TYPE_TEXT
305 && this.getDownloadable() == null
306 && message.getDownloadable() == null
307 && message.getEncryption() != Message.ENCRYPTION_PGP
308 && this.getType() == message.getType()
309 && this.getEncryption() == message.getEncryption()
310 && this.getCounterpart() != null
311 && this.getCounterpart().equals(message.getCounterpart())
312 && (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && ((this
313 .getStatus() == message.getStatus() || ((this.getStatus() == Message.STATUS_SEND || this
314 .getStatus() == Message.STATUS_SEND_RECEIVED) && (message
315 .getStatus() == Message.STATUS_UNSEND
316 || message.getStatus() == Message.STATUS_SEND || message
317 .getStatus() == Message.STATUS_SEND_DISPLAYED))))
318 && !message.bodyContainsDownloadable()
319 && !this.bodyContainsDownloadable());
320 }
321
322 public String getMergedBody() {
323 Message next = this.next();
324 if (this.mergeable(next)) {
325 return body.trim() + '\n' + next.getMergedBody();
326 }
327 return body.trim();
328 }
329
330 public int getMergedStatus() {
331 Message next = this.next();
332 if (this.mergeable(next)) {
333 return next.getMergedStatus();
334 } else {
335 return getStatus();
336 }
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_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_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}